(function () {
    function MainController($scope, $rootScope, $location, $routeParams, $timeout, $http, $log, appState, treeService, notificationsService, userService, navigationService, historyService, updateChecker, assetsService, eventsService, umbRequestHelper, tmhDynamicLocale, localStorageService, tourService) {
        //the null is important because we do an explicit bool check on this in the view
        //the avatar is by default the umbraco logo    
        $scope.authenticated = null;
        $scope.avatar = [
            { value: 'assets/img/application/logo.png' },
            { value: 'assets/img/application/logo@2x.png' },
            { value: 'assets/img/application/logo@3x.png' }
        ];
        $scope.touchDevice = appState.getGlobalState('touchDevice');
        $scope.removeNotification = function (index) {
            notificationsService.remove(index);
        };
        $scope.closeDialogs = function (event) {
            //only close dialogs if non-link and non-buttons are clicked
            var el = event.target.nodeName;
            var els = [
                'INPUT',
                'A',
                'BUTTON'
            ];
            if (els.indexOf(el) >= 0) {
                return;
            }
            var parents = $(event.target).parents('a,button');
            if (parents.length > 0) {
                return;
            }
            //SD: I've updated this so that we don't close the dialog when clicking inside of the dialog
            var nav = $(event.target).parents('#dialog');
            if (nav.length === 1) {
                return;
            }
            eventsService.emit('app.closeDialogs', event);
        };
        var evts = [];
        //when a user logs out or timesout
        evts.push(eventsService.on('app.notAuthenticated', function () {
            $scope.authenticated = null;
            $scope.user = null;
        }));
        evts.push(eventsService.on('app.userRefresh', function (evt) {
            userService.refreshCurrentUser().then(function (data) {
                $scope.user = data;
                //Load locale file
                if ($scope.user.locale) {
                    tmhDynamicLocale.set($scope.user.locale);
                }
                if ($scope.user.avatars) {
                    $scope.avatar = [];
                    if (angular.isArray($scope.user.avatars)) {
                        for (var i = 0; i < $scope.user.avatars.length; i++) {
                            $scope.avatar.push({ value: $scope.user.avatars[i] });
                        }
                    }
                }
            });
        }));
        //when the app is ready/user is logged in, setup the data
        evts.push(eventsService.on('app.ready', function (evt, data) {
            $scope.authenticated = data.authenticated;
            $scope.user = data.user;
            updateChecker.check().then(function (update) {
                if (update && update !== 'null') {
                    if (update.type !== 'None') {
                        var notification = {
                            headline: 'Update available',
                            message: 'Click to download',
                            sticky: true,
                            type: 'info',
                            url: update.url
                        };
                        notificationsService.add(notification);
                    }
                }
            });
            //if the user has changed we need to redirect to the root so they don't try to continue editing the
            //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true)
            if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) {
                $location.path('/').search('');
                historyService.removeAll();
                treeService.clearCache();
                //if the user changed, clearout local storage too - could contain sensitive data
                localStorageService.clearAll();
            }
            //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data
            if (data.loginType === 'credentials') {
                localStorageService.clearAll();
            }
            //Load locale file
            if ($scope.user.locale) {
                tmhDynamicLocale.set($scope.user.locale);
            }
            if ($scope.user.avatars) {
                $scope.avatar = [];
                if (angular.isArray($scope.user.avatars)) {
                    for (var i = 0; i < $scope.user.avatars.length; i++) {
                        $scope.avatar.push({ value: $scope.user.avatars[i] });
                    }
                }
            }
        }));
        evts.push(eventsService.on('app.ysod', function (name, error) {
            $scope.ysodOverlay = {
                view: 'ysod',
                error: error,
                show: true
            };
        }));
        // manage the help dialog by subscribing to the showHelp appState
        $scope.drawer = {};
        evts.push(eventsService.on('appState.drawerState.changed', function (e, args) {
            // set view
            if (args.key === 'view') {
                $scope.drawer.view = args.value;
            }
            // set custom model
            if (args.key === 'model') {
                $scope.drawer.model = args.value;
            }
            // show / hide drawer
            if (args.key === 'showDrawer') {
                $scope.drawer.show = args.value;
            }
        }));
        evts.push(eventsService.on('appState.tour.start', function (name, args) {
            $scope.tour = args;
            $scope.tour.show = true;
        }));
        evts.push(eventsService.on('appState.tour.end', function () {
            $scope.tour = null;
        }));
        evts.push(eventsService.on('appState.tour.complete', function () {
            $scope.tour = null;
        }));
        evts.push(eventsService.on('appState.backdrop', function (name, args) {
            $scope.backdrop = args;
        }));
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    //register it
    angular.module('umbraco').controller('Umbraco.MainController', MainController).config(function (tmhDynamicLocaleProvider) {
        //Set url for locale files
        tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js');
    });
    /**
 * @ngdoc controller
 * @name Umbraco.NavigationController
 * @function
 *
 * @description
 * Handles the section area of the app
 *
 * @param {navigationService} navigationService A reference to the navigationService
 */
    function NavigationController($scope, $rootScope, $location, $log, $routeParams, $timeout, appState, navigationService, keyboardService, dialogService, historyService, eventsService, sectionResource, angularHelper) {
        //TODO: Need to think about this and an nicer way to achieve what this is doing.
        //the tree event handler i used to subscribe to the main tree click events
        $scope.treeEventHandler = $({});
        navigationService.setupTreeEvents($scope.treeEventHandler);
        //Put the navigation service on this scope so we can use it's methods/properties in the view.
        // IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since
        //   when we create a dialog we pass in this scope to be used for the dialog's scope instead of creating a new one.
        $scope.nav = navigationService;
        // TODO: Lets fix this, it is less than ideal to be passing in the navigationController scope to something else to be used as it's scope,
        // this is going to lead to problems/confusion. I really don't think passing scope's around is very good practice.
        $rootScope.nav = navigationService;
        //set up our scope vars
        $scope.showContextMenuDialog = false;
        $scope.showContextMenu = false;
        $scope.showSearchResults = false;
        $scope.menuDialogTitle = null;
        $scope.menuActions = [];
        $scope.menuNode = null;
        $scope.currentSection = appState.getSectionState('currentSection');
        $scope.showNavigation = appState.getGlobalState('showNavigation');
        //trigger search with a hotkey:
        keyboardService.bind('ctrl+shift+s', function () {
            navigationService.showSearch();
        });
        //trigger dialogs with a hotkey:
        keyboardService.bind('esc', function () {
            eventsService.emit('app.closeDialogs');
        });
        $scope.selectedId = navigationService.currentId;
        var evts = [];
        //Listen for global state changes
        evts.push(eventsService.on('appState.globalState.changed', function (e, args) {
            if (args.key === 'showNavigation') {
                $scope.showNavigation = args.value;
            }
        }));
        //Listen for menu state changes
        evts.push(eventsService.on('appState.menuState.changed', function (e, args) {
            if (args.key === 'showMenuDialog') {
                $scope.showContextMenuDialog = args.value;
            }
            if (args.key === 'showMenu') {
                $scope.showContextMenu = args.value;
            }
            if (args.key === 'dialogTitle') {
                $scope.menuDialogTitle = args.value;
            }
            if (args.key === 'menuActions') {
                $scope.menuActions = args.value;
            }
            if (args.key === 'currentNode') {
                $scope.menuNode = args.value;
            }
        }));
        //Listen for section state changes
        evts.push(eventsService.on('appState.treeState.changed', function (e, args) {
            var f = args;
            if (args.value.root && args.value.root.metaData.containsTrees === false) {
                $rootScope.emptySection = true;
            } else {
                $rootScope.emptySection = false;
            }
        }));
        //Listen for section state changes
        evts.push(eventsService.on('appState.sectionState.changed', function (e, args) {
            //section changed
            if (args.key === 'currentSection') {
                $scope.currentSection = args.value;
            }
            //show/hide search results
            if (args.key === 'showSearchResults') {
                $scope.showSearchResults = args.value;
            }
        }));
        //This reacts to clicks passed to the body element which emits a global call to close all dialogs
        evts.push(eventsService.on('app.closeDialogs', function (event) {
            if (appState.getGlobalState('stickyNavigation')) {
                navigationService.hideNavigation();
                //TODO: don't know why we need this? - we are inside of an angular event listener.
                angularHelper.safeApply($scope);
            }
        }));
        //when a user logs out or timesout
        evts.push(eventsService.on('app.notAuthenticated', function () {
            $scope.authenticated = false;
        }));
        //when the application is ready and the user is authorized, setup the data
        evts.push(eventsService.on('app.ready', function (evt, data) {
            $scope.authenticated = true;
        }));
        //this reacts to the options item in the tree
        //todo, migrate to nav service
        $scope.searchShowMenu = function (ev, args) {
            //always skip default
            args.skipDefault = true;
            navigationService.showMenu(ev, args);
        };
        //todo, migrate to nav service
        $scope.searchHide = function () {
            navigationService.hideSearch();
        };
        //the below assists with hiding/showing the tree
        var treeActive = false;
        //Sets a service variable as soon as the user hovers the navigation with the mouse
        //used by the leaveTree method to delay hiding
        $scope.enterTree = function (event) {
            treeActive = true;
        };
        // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again
        $scope.leaveTree = function (event) {
            //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down
            if (!event) {
                return;
            }
            if (!appState.getGlobalState('touchDevice')) {
                treeActive = false;
                $timeout(function () {
                    if (!treeActive) {
                        navigationService.hideTree();
                    }
                }, 300);
            }
        };
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    //register it
    angular.module('umbraco').controller('Umbraco.NavigationController', NavigationController);
    /**
 * @ngdoc controller
 * @name Umbraco.SearchController
 * @function
 * 
 * @description
 * Controls the search functionality in the site
 *  
 */
    function SearchController($scope, searchService, $log, $location, navigationService, $q) {
        $scope.searchTerm = null;
        $scope.searchResults = [];
        $scope.isSearching = false;
        $scope.selectedResult = -1;
        $scope.navigateResults = function (ev) {
            //38: up 40: down, 13: enter
            switch (ev.keyCode) {
            case 38:
                iterateResults(true);
                break;
            case 40:
                iterateResults(false);
                break;
            case 13:
                if ($scope.selectedItem) {
                    $location.path($scope.selectedItem.editorPath);
                    navigationService.hideSearch();
                }
                break;
            }
        };
        var group = undefined;
        var groupNames = [];
        var groupIndex = -1;
        var itemIndex = -1;
        $scope.selectedItem = undefined;
        $scope.clearSearch = function () {
            $scope.searchTerm = null;
        };
        function iterateResults(up) {
            //default group
            if (!group) {
                for (var g in $scope.groups) {
                    if ($scope.groups.hasOwnProperty(g)) {
                        groupNames.push(g);
                    }
                }
                //Sorting to match the groups order
                groupNames.sort();
                group = $scope.groups[groupNames[0]];
                groupIndex = 0;
            }
            if (up) {
                if (itemIndex === 0) {
                    if (groupIndex === 0) {
                        gotoGroup(Object.keys($scope.groups).length - 1, true);
                    } else {
                        gotoGroup(groupIndex - 1, true);
                    }
                } else {
                    gotoItem(itemIndex - 1);
                }
            } else {
                if (itemIndex < group.results.length - 1) {
                    gotoItem(itemIndex + 1);
                } else {
                    if (groupIndex === Object.keys($scope.groups).length - 1) {
                        gotoGroup(0);
                    } else {
                        gotoGroup(groupIndex + 1);
                    }
                }
            }
        }
        function gotoGroup(index, up) {
            groupIndex = index;
            group = $scope.groups[groupNames[groupIndex]];
            if (up) {
                gotoItem(group.results.length - 1);
            } else {
                gotoItem(0);
            }
        }
        function gotoItem(index) {
            itemIndex = index;
            $scope.selectedItem = group.results[itemIndex];
        }
        //used to cancel any request in progress if another one needs to take it's place
        var canceler = null;
        $scope.$watch('searchTerm', _.debounce(function (newVal, oldVal) {
            $scope.$apply(function () {
                $scope.hasResults = false;
                if ($scope.searchTerm) {
                    if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                        //Resetting for brand new search
                        group = undefined;
                        groupNames = [];
                        groupIndex = -1;
                        itemIndex = -1;
                        $scope.isSearching = true;
                        navigationService.showSearch();
                        $scope.selectedItem = undefined;
                        //a canceler exists, so perform the cancelation operation and reset
                        if (canceler) {
                            canceler.resolve();
                            canceler = $q.defer();
                        } else {
                            canceler = $q.defer();
                        }
                        searchService.searchAll({
                            term: $scope.searchTerm,
                            canceler: canceler
                        }).then(function (result) {
                            //result is a dictionary of group Title and it's results
                            var filtered = {};
                            _.each(result, function (value, key) {
                                if (value.results.length > 0) {
                                    filtered[key] = value;
                                }
                            });
                            $scope.groups = filtered;
                            // check if search has results
                            $scope.hasResults = Object.keys($scope.groups).length > 0;
                            //set back to null so it can be re-created
                            canceler = null;
                            $scope.isSearching = false;
                        });
                    }
                } else {
                    $scope.isSearching = false;
                    navigationService.hideSearch();
                    $scope.selectedItem = undefined;
                }
            });
        }, 200));
    }
    //register it
    angular.module('umbraco').controller('Umbraco.SearchController', SearchController);
    /**
 * @ngdoc controller
 * @name Umbraco.MainController
 * @function
 * 
 * @description
 * The controller for the AuthorizeUpgrade login page
 * 
 */
    function AuthorizeUpgradeController($scope, $window) {
        //Add this method to the scope - this method will be called by the login dialog controller when the login is successful
        // then we'll handle the redirect.
        $scope.submit = function (event) {
            var qry = $window.location.search.trimStart('?').split('&');
            var redir = _.find(qry, function (item) {
                return item.startsWith('redir=');
            });
            if (redir) {
                $window.location = decodeURIComponent(redir.split('=')[1]);
            } else {
                $window.location = '/';
            }
        };
    }
    angular.module('umbraco').controller('Umbraco.AuthorizeUpgradeController', AuthorizeUpgradeController);
    /**
 * @ngdoc controller
 * @name Umbraco.DashboardController
 * @function
 * 
 * @description
 * Controls the dashboards of the application
 * 
 */
    function DashboardController($scope, $routeParams, dashboardResource, localizationService) {
        $scope.page = {};
        $scope.page.nameLocked = true;
        $scope.page.loading = true;
        $scope.dashboard = {};
        localizationService.localize('sections_' + $routeParams.section).then(function (name) {
            $scope.dashboard.name = name;
        });
        dashboardResource.getDashboard($routeParams.section).then(function (tabs) {
            $scope.dashboard.tabs = tabs;
            $scope.page.loading = false;
        });
    }
    //register it
    angular.module('umbraco').controller('Umbraco.DashboardController', DashboardController);
    angular.module('umbraco').controller('Umbraco.Dialogs.ApprovedColorPickerController', function ($scope, $http, umbPropEditorHelper, assetsService) {
        assetsService.loadJs('lib/cssparser/cssparser.js', $scope).then(function () {
            var cssPath = $scope.dialogData.cssPath;
            $scope.cssClass = $scope.dialogData.cssClass;
            $scope.classes = [];
            $scope.change = function (newClass) {
                $scope.model.value = newClass;
            };
            $http.get(cssPath).success(function (data) {
                var parser = new CSSParser();
                $scope.classes = parser.parse(data, false, false).cssRules;
                $scope.classes.splice(0, 0, 'noclass');
            });
            assetsService.loadCss(cssPath, $scope);
        });
    });
    function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
        $scope.defaultButton = null;
        $scope.subButtons = [];
        var dialogOptions = $scope.$parent.dialogOptions;
        // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
        function performSave(args) {
            contentEditingHelper.contentEditorPerformSave({
                statusMessage: args.statusMessage,
                saveMethod: args.saveMethod,
                scope: $scope,
                content: $scope.content
            }).then(function (content) {
                //success            
                if (dialogOptions.closeOnSave) {
                    $scope.submit(content);
                }
            }, function (err) {
            });
        }
        function filterTabs(entity, blackList) {
            if (blackList) {
                _.each(entity.tabs, function (tab) {
                    tab.hide = _.contains(blackList, tab.alias);
                });
            }
            return entity;
        }
        ;
        function init(content) {
            var buttons = contentEditingHelper.configureContentEditorButtons({
                create: $routeParams.create,
                content: content,
                methods: {
                    saveAndPublish: $scope.saveAndPublish,
                    sendToPublish: $scope.sendToPublish,
                    save: $scope.save,
                    unPublish: angular.noop
                }
            });
            $scope.defaultButton = buttons.defaultButton;
            $scope.subButtons = buttons.subButtons;
            //This is a total hack but we have really no other way of sharing data to the property editors of this
            // content item, so we'll just set the property on the content item directly
            $scope.content.isDialogEditor = true;
            editorState.set($scope.content);
        }
        //check if the entity is being passed in, otherwise load it from the server
        if (angular.isObject(dialogOptions.entity)) {
            $scope.loaded = true;
            $scope.content = filterTabs(dialogOptions.entity, dialogOptions.tabFilter);
            init($scope.content);
        } else {
            contentResource.getById(dialogOptions.id).then(function (data) {
                $scope.loaded = true;
                $scope.content = filterTabs(data, dialogOptions.tabFilter);
                init($scope.content);
                //in one particular special case, after we've created a new item we redirect back to the edit
                // route but there might be server validation errors in the collection which we need to display
                // after the redirect, so we will bind all subscriptions which will show the server validation errors
                // if there are any and then clear them so the collection no longer persists them.
                serverValidationManager.executeAndClearAllSubscriptions();
            });
        }
        $scope.sendToPublish = function () {
            performSave({
                saveMethod: contentResource.sendToPublish,
                statusMessage: 'Sending...'
            });
        };
        $scope.saveAndPublish = function () {
            performSave({
                saveMethod: contentResource.publish,
                statusMessage: 'Publishing...'
            });
        };
        $scope.save = function () {
            performSave({
                saveMethod: contentResource.save,
                statusMessage: 'Saving...'
            });
        };
        // this method is called for all action buttons and then we proxy based on the btn definition
        $scope.performAction = function (btn) {
            if (!btn || !angular.isFunction(btn.handler)) {
                throw 'btn.handler must be a function reference';
            }
            if (!$scope.busy) {
                btn.handler.apply(this);
            }
        };
    }
    angular.module('umbraco').controller('Umbraco.Dialogs.Content.EditController', ContentEditDialogController);
    angular.module('umbraco').controller('Umbraco.Dialogs.HelpController', function ($scope, $location, $routeParams, helpService, userService, localizationService) {
        $scope.section = $routeParams.section;
        $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
        if (!$scope.section) {
            $scope.section = 'content';
        }
        $scope.sectionName = $scope.section;
        var rq = {};
        rq.section = $scope.section;
        //translate section name
        localizationService.localize('sections_' + rq.section).then(function (value) {
            $scope.sectionName = value;
        });
        userService.getCurrentUser().then(function (user) {
            rq.lang = user.locale;
            if ($routeParams.url) {
                rq.path = decodeURIComponent($routeParams.url);
                if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                    rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                }
                if (rq.path.indexOf('.aspx') > 0) {
                    rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                }
            } else {
                rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
            }
            helpService.findHelp(rq).then(function (topics) {
                $scope.topics = topics;
            });
            helpService.findVideos(rq).then(function (videos) {
                $scope.videos = videos;
            });
        });
    });
    //used for the icon picker dialog
    angular.module('umbraco').controller('Umbraco.Dialogs.IconPickerController', function ($scope, iconHelper) {
        iconHelper.getIcons().then(function (icons) {
            $scope.icons = icons;
        });
        $scope.submitClass = function (icon) {
            if ($scope.color) {
                $scope.submit(icon + ' ' + $scope.color);
            } else {
                $scope.submit(icon);
            }
        };
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Dialogs.InsertMacroController
 * @function
 * 
 * @description
 * The controller for the custom insert macro dialog. Until we upgrade the template editor to be angular this 
 * is actually loaded into an iframe with full html.
 */
    function InsertMacroController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper) {
        /** changes the view to edit the params of the selected macro */
        /** if there is pnly one macro, and it has parameters - editor can skip selecting the Macro **/
        function editParams(insertIfNoParameters) {
            //whether to insert the macro in the rich text editor when editParams is called and there are no parameters see U4-10537 
            insertIfNoParameters = typeof insertIfNoParameters !== 'undefined' ? insertIfNoParameters : true;
            //get the macro params if there are any
            macroResource.getMacroParameters($scope.selectedMacro.id).then(function (data) {
                //go to next page if there are params otherwise we can just exit
                if (!angular.isArray(data) || data.length === 0) {
                    //we can just exist!                  
                    if (insertIfNoParameters) {
                        submitForm();
                    } else {
                        $scope.wizardStep = 'macroSelect';
                    }
                } else {
                    $scope.wizardStep = 'paramSelect';
                    $scope.macroParams = data;
                    //fill in the data if we are editing this macro
                    if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroParamsDictionary) {
                        _.each($scope.dialogData.macroData.macroParamsDictionary, function (val, key) {
                            var prop = _.find($scope.macroParams, function (item) {
                                return item.alias == key;
                            });
                            if (prop) {
                                if (_.isString(val)) {
                                    //we need to unescape values as they have most likely been escaped while inserted 
                                    val = _.unescape(val);
                                    //detect if it is a json string
                                    if (val.detectIsJson()) {
                                        try {
                                            //Parse it to json
                                            prop.value = angular.fromJson(val);
                                        } catch (e) {
                                            // not json
                                            prop.value = val;
                                        }
                                    } else {
                                        prop.value = val;
                                    }
                                } else {
                                    prop.value = val;
                                }
                            }
                        });
                    }
                }
            });
        }
        /** submit the filled out macro params */
        function submitForm() {
            //collect the value data, close the dialog and send the data back to the caller
            //create a dictionary for the macro params
            var paramDictionary = {};
            _.each($scope.macroParams, function (item) {
                var val = item.value;
                if (item.value != null && item.value != undefined && !_.isString(item.value)) {
                    try {
                        val = angular.toJson(val);
                    } catch (e) {
                    }
                }
                //each value needs to be xml escaped!! since the value get's stored as an xml attribute
                paramDictionary[item.alias] = _.escape(val);
            });
            //need to find the macro alias for the selected id
            var macroAlias = $scope.selectedMacro.alias;
            //get the syntax based on the rendering engine
            var syntax;
            if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === 'WebForms') {
                syntax = macroService.generateWebFormsSyntax({
                    macroAlias: macroAlias,
                    macroParamsDictionary: paramDictionary
                });
            } else if ($scope.dialogData.renderingEngine && $scope.dialogData.renderingEngine === 'Mvc') {
                syntax = macroService.generateMvcSyntax({
                    macroAlias: macroAlias,
                    macroParamsDictionary: paramDictionary
                });
            } else {
                syntax = macroService.generateMacroSyntax({
                    macroAlias: macroAlias,
                    macroParamsDictionary: paramDictionary
                });
            }
            $scope.submit({
                syntax: syntax,
                macroAlias: macroAlias,
                macroParamsDictionary: paramDictionary
            });
        }
        $scope.macros = [];
        $scope.selectedMacro = null;
        $scope.wizardStep = 'macroSelect';
        $scope.macroParams = [];
        $scope.submitForm = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                formHelper.resetForm({ scope: $scope });
                if ($scope.wizardStep === 'macroSelect') {
                    editParams(true);
                } else {
                    submitForm();
                }
            }
        };
        //here we check to see if we've been passed a selected macro and if so we'll set the
        //editor to start with parameter editing
        if ($scope.dialogData && $scope.dialogData.macroData) {
            $scope.wizardStep = 'paramSelect';
        }
        //get the macro list - pass in a filter if it is only for rte
        entityResource.getAll('Macro', $scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) {
            //if 'allowedMacros' is specified, we need to filter
            if (angular.isArray($scope.dialogData.allowedMacros) && $scope.dialogData.allowedMacros.length > 0) {
                $scope.macros = _.filter(data, function (d) {
                    return _.contains($scope.dialogData.allowedMacros, d.alias);
                });
            } else {
                $scope.macros = data;
            }
            //check if there's a pre-selected macro and if it exists
            if ($scope.dialogData && $scope.dialogData.macroData && $scope.dialogData.macroData.macroAlias) {
                var found = _.find(data, function (item) {
                    return item.alias === $scope.dialogData.macroData.macroAlias;
                });
                if (found) {
                    //select the macro and go to next screen
                    $scope.selectedMacro = found;
                    editParams(true);
                    return;
                }
            }
            //if there is only one macro in the site and it has parameters, let's not make the editor choose it from a selection of one macro (unless there are no parameters - then weirdly it's a better experience to make that selection)
            if ($scope.macros.length == 1) {
                $scope.selectedMacro = $scope.macros[0];
                editParams(false);
            } else {
                //we don't have a pre-selected macro so ensure the correct step is set
                $scope.wizardStep = 'macroSelect';
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Dialogs.InsertMacroController', InsertMacroController);
    /**
 * @ngdoc controller
 * @name Umbraco.Dialogs.LegacyDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function LegacyDeleteController($scope, legacyResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            legacyResource.deleteItem({
                nodeId: $scope.currentNode.id,
                nodeType: $scope.currentNode.nodeType,
                alias: $scope.currentNode.name
            }).then(function () {
                $scope.currentNode.loading = false;
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Dialogs.LegacyDeleteController', LegacyDeleteController);
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Dialogs.LinkPickerController', function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
        var dialogOptions = $scope.dialogOptions;
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        $scope.dialogTreeEventHandler = $({});
        $scope.target = {};
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        if (dialogOptions.currentTarget) {
            $scope.target = dialogOptions.currentTarget;
            //if we have a node ID, we fetch the current node to build the form data
            if ($scope.target.id || $scope.target.udi) {
                var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
                if (!$scope.target.path) {
                    entityResource.getPath(id, 'Document').then(function (path) {
                        $scope.target.path = path;
                        //now sync the tree to this path
                        $scope.dialogTreeEventHandler.syncTree({
                            path: $scope.target.path,
                            tree: 'content'
                        });
                    });
                }
                // if a link exists, get the properties to build the anchor name list
                contentResource.getById(id).then(function (resp) {
                    $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
                    $scope.target.url = resp.urls[0];
                });
            } else if ($scope.target.url.length) {
                // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
                // only do the substring if there's a # or a ?
                var indexOfAnchor = $scope.target.url.search(/(#|\?)/);
                if (indexOfAnchor > -1) {
                    // populate the anchor
                    $scope.target.anchor = $scope.target.url.substring(indexOfAnchor);
                    // then rewrite the model and populate the link
                    $scope.target.url = $scope.target.url.substring(0, indexOfAnchor);
                }
            }
        }
        if (dialogOptions.anchors) {
            $scope.anchorValues = dialogOptions.anchors;
        }
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if (args.node.metaData.listViewNode) {
                //check if list view 'search' node was selected
                $scope.searchInfo.showSearch = true;
                $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
                $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
            } else {
                eventsService.emit('dialogs.linkPicker.select', args);
                if ($scope.currentNode) {
                    //un-select if there's a current one selected
                    $scope.currentNode.selected = false;
                }
                $scope.currentNode = args.node;
                $scope.currentNode.selected = true;
                $scope.target.id = args.node.id;
                $scope.target.udi = args.node.udi;
                $scope.target.name = args.node.name;
                if (args.node.id < 0) {
                    $scope.target.url = '/';
                } else {
                    contentResource.getById(args.node.id).then(function (resp) {
                        $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
                        $scope.target.url = resp.urls[0];
                    });
                }
                if (!angular.isUndefined($scope.target.isMedia)) {
                    delete $scope.target.isMedia;
                }
            }
        }
        function nodeExpandedHandler(ev, args) {
            if (angular.isArray(args.children)) {
                //iterate children
                _.each(args.children, function (child) {
                    //check if any of the items are list views, if so we need to add a custom 
                    // child: A node to activate the search
                    if (child.metaData.isContainer) {
                        child.hasChildren = true;
                        child.children = [{
                                level: child.level + 1,
                                hasChildren: false,
                                name: searchText,
                                metaData: { listViewNode: child },
                                cssClass: 'icon umb-tree-icon sprTree icon-search',
                                cssClasses: ['not-published']
                            }];
                    }
                });
            }
        }
        $scope.switchToMediaPicker = function () {
            userService.getCurrentUser().then(function (userData) {
                dialogService.mediaPicker({
                    startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0],
                    callback: function (media) {
                        $scope.target.id = media.id;
                        $scope.target.isMedia = true;
                        $scope.target.name = media.name;
                        $scope.target.url = mediaHelper.resolveFile(media);
                    }
                });
            });
        };
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
    });
    angular.module('umbraco').controller('Umbraco.Dialogs.LoginController', function ($scope, $cookies, $location, currentUserResource, formHelper, mediaHelper, umbRequestHelper, Upload, localizationService, userService, externalLoginInfo, resetPasswordCodeInfo, $timeout, authResource, dialogService, $q) {
        $scope.invitedUser = null;
        $scope.invitedUserPasswordModel = {
            password: '',
            confirmPassword: '',
            buttonState: '',
            passwordPolicies: null,
            passwordPolicyText: ''
        };
        $scope.loginStates = { submitButton: 'init' };
        $scope.avatarFile = {
            filesHolder: null,
            uploadStatus: null,
            uploadProgress: 0,
            maxFileSize: Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB',
            acceptedFileTypes: mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes),
            uploaded: false
        };
        $scope.togglePassword = function () {
            var elem = $('form[name=\'loginForm\'] input[name=\'password\']');
            elem.attr('type', elem.attr('type') === 'text' ? 'password' : 'text');
            $('.password-text.show, .password-text.hide').toggle();
        };
        function init() {
            // Check if it is a new user
            var inviteVal = $location.search().invite;
            //1 = enter password, 2 = password set, 3 = invalid token
            if (inviteVal && (inviteVal === '1' || inviteVal === '2')) {
                $q.all([
                    //get the current invite user
                    authResource.getCurrentInvitedUser().then(function (data) {
                        $scope.invitedUser = data;
                    }, function () {
                        //it failed so we should remove the search
                        $location.search('invite', null);
                    }),
                    //get the membership provider config for password policies
                    authResource.getMembershipProviderConfig().then(function (data) {
                        $scope.invitedUserPasswordModel.passwordPolicies = data;
                        //localize the text
                        localizationService.localize('errorHandling_errorInPasswordFormat', [
                            $scope.invitedUserPasswordModel.passwordPolicies.minPasswordLength,
                            $scope.invitedUserPasswordModel.passwordPolicies.minNonAlphaNumericChars
                        ]).then(function (data) {
                            $scope.invitedUserPasswordModel.passwordPolicyText = data;
                        });
                    })
                ]).then(function () {
                    $scope.inviteStep = Number(inviteVal);
                });
            } else if (inviteVal && inviteVal === '3') {
                $scope.inviteStep = Number(inviteVal);
            }
        }
        $scope.changeAvatar = function (files, event) {
            if (files && files.length > 0) {
                upload(files[0]);
            }
        };
        $scope.getStarted = function () {
            $location.search('invite', null);
            $scope.submit(true);
        };
        function upload(file) {
            $scope.avatarFile.uploadProgress = 0;
            Upload.upload({
                url: umbRequestHelper.getApiUrl('currentUserApiBaseUrl', 'PostSetAvatar'),
                fields: {},
                file: file
            }).progress(function (evt) {
                if ($scope.avatarFile.uploadStatus !== 'done' && $scope.avatarFile.uploadStatus !== 'error') {
                    // set uploading status on file
                    $scope.avatarFile.uploadStatus = 'uploading';
                    // calculate progress in percentage
                    var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                    // set percentage property on file
                    $scope.avatarFile.uploadProgress = progressPercentage;
                }
            }).success(function (data, status, headers, config) {
                $scope.avatarFile.uploadProgress = 100;
                // set done status on file
                $scope.avatarFile.uploadStatus = 'done';
                $scope.invitedUser.avatars = data;
                $scope.avatarFile.uploaded = true;
            }).error(function (evt, status, headers, config) {
                // set status done
                $scope.avatarFile.uploadStatus = 'error';
                // If file not found, server will return a 404 and display this message
                if (status === 404) {
                    $scope.avatarFile.serverErrorMessage = 'File not found';
                } else if (status == 400) {
                    //it's a validation error
                    $scope.avatarFile.serverErrorMessage = evt.message;
                } else {
                    //it's an unhandled error
                    //if the service returns a detailed error
                    if (evt.InnerException) {
                        $scope.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                        //Check if its the common "too large file" exception
                        if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                            $scope.avatarFile.serverErrorMessage = 'File too large to upload';
                        }
                    } else if (evt.Message) {
                        $scope.avatarFile.serverErrorMessage = evt.Message;
                    }
                }
            });
        }
        $scope.inviteSavePassword = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                $scope.invitedUserPasswordModel.buttonState = 'busy';
                currentUserResource.performSetInvitedUserPassword($scope.invitedUserPasswordModel.password).then(function (data) {
                    //success
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    $scope.invitedUserPasswordModel.buttonState = 'success';
                    //set the user and set them as logged in
                    $scope.invitedUser = data;
                    userService.setAuthenticationSuccessful(data);
                    $scope.inviteStep = 2;
                }, function (err) {
                    //error
                    formHelper.handleError(err);
                    $scope.invitedUserPasswordModel.buttonState = 'error';
                });
            }
        };
        var setFieldFocus = function (form, field) {
            $timeout(function () {
                $('form[name=\'' + form + '\'] input[name=\'' + field + '\']').focus();
            });
        };
        var twoFactorloginDialog = null;
        function show2FALoginDialog(view, callback) {
            if (!twoFactorloginDialog) {
                twoFactorloginDialog = dialogService.open({
                    //very special flag which means that global events cannot close this dialog
                    manualClose: true,
                    template: view,
                    modalClass: 'login-overlay',
                    animation: 'slide',
                    show: true,
                    callback: callback
                });
            }
        }
        function resetInputValidation() {
            $scope.confirmPassword = '';
            $scope.password = '';
            $scope.login = '';
            if ($scope.loginForm) {
                $scope.loginForm.username.$setValidity('auth', true);
                $scope.loginForm.password.$setValidity('auth', true);
            }
            if ($scope.requestPasswordResetForm) {
                $scope.requestPasswordResetForm.email.$setValidity('auth', true);
            }
            if ($scope.setPasswordForm) {
                $scope.setPasswordForm.password.$setValidity('auth', true);
                $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
            }
        }
        $scope.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset;
        $scope.showLogin = function () {
            $scope.errorMsg = '';
            resetInputValidation();
            $scope.view = 'login';
            setFieldFocus('loginForm', 'username');
        };
        $scope.showRequestPasswordReset = function () {
            $scope.errorMsg = '';
            resetInputValidation();
            $scope.view = 'request-password-reset';
            $scope.showEmailResetConfirmation = false;
            setFieldFocus('requestPasswordResetForm', 'email');
        };
        $scope.showSetPassword = function () {
            $scope.errorMsg = '';
            resetInputValidation();
            $scope.view = 'set-password';
            setFieldFocus('setPasswordForm', 'password');
        };
        var d = new Date();
        var konamiGreetings = new Array('Suze Sunday', 'Malibu Monday', 'Tequila Tuesday', 'Whiskey Wednesday', 'Negroni Day', 'Fernet Friday', 'Sancerre Saturday');
        var konamiMode = $cookies.konamiLogin;
        if (konamiMode == '1') {
            $scope.greeting = 'Happy ' + konamiGreetings[d.getDay()];
        } else {
            localizationService.localize('login_greeting' + d.getDay()).then(function (label) {
                $scope.greeting = label;
            });    // weekday[d.getDay()];
        }
        $scope.errorMsg = '';
        $scope.externalLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLoginsUrl;
        $scope.externalLoginProviders = externalLoginInfo.providers;
        $scope.externalLoginInfo = externalLoginInfo;
        $scope.resetPasswordCodeInfo = resetPasswordCodeInfo;
        $scope.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage;
        $scope.activateKonamiMode = function () {
            if ($cookies.konamiLogin == '1') {
                // somehow I can't update the cookie value using $cookies, so going native
                document.cookie = 'konamiLogin=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
                document.location.reload();
            } else {
                document.cookie = 'konamiLogin=1; expires=Tue, 01 Jan 2030 00:00:01 GMT;';
                $scope.$apply(function () {
                    $scope.greeting = 'Happy ' + konamiGreetings[d.getDay()];
                });
            }
        };
        $scope.loginSubmit = function (login, password) {
            //TODO: Do validation properly like in the invite password update
            //if the login and password are not empty we need to automatically
            // validate them - this is because if there are validation errors on the server
            // then the user has to change both username & password to resubmit which isn't ideal,
            // so if they're not empty, we'll just make sure to set them to valid.
            if (login && password && login.length > 0 && password.length > 0) {
                $scope.loginForm.username.$setValidity('auth', true);
                $scope.loginForm.password.$setValidity('auth', true);
            }
            if ($scope.loginForm.$invalid) {
                return;
            }
            $scope.loginStates.submitButton = 'busy';
            userService.authenticate(login, password).then(function (data) {
                $scope.loginStates.submitButton = 'success';
                $scope.submit(true);
            }, function (reason) {
                //is Two Factor required?
                if (reason.status === 402) {
                    $scope.errorMsg = 'Additional authentication required';
                    show2FALoginDialog(reason.data.twoFactorView, $scope.submit);
                } else {
                    $scope.loginStates.submitButton = 'error';
                    $scope.errorMsg = reason.errorMsg;
                    //set the form inputs to invalid
                    $scope.loginForm.username.$setValidity('auth', false);
                    $scope.loginForm.password.$setValidity('auth', false);
                }
            });
            //setup a watch for both of the model values changing, if they change
            // while the form is invalid, then revalidate them so that the form can
            // be submitted again.
            $scope.loginForm.username.$viewChangeListeners.push(function () {
                if ($scope.loginForm.$invalid) {
                    $scope.loginForm.username.$setValidity('auth', true);
                    $scope.loginForm.password.$setValidity('auth', true);
                }
            });
            $scope.loginForm.password.$viewChangeListeners.push(function () {
                if ($scope.loginForm.$invalid) {
                    $scope.loginForm.username.$setValidity('auth', true);
                    $scope.loginForm.password.$setValidity('auth', true);
                }
            });
        };
        $scope.requestPasswordResetSubmit = function (email) {
            //TODO: Do validation properly like in the invite password update
            if (email && email.length > 0) {
                $scope.requestPasswordResetForm.email.$setValidity('auth', true);
            }
            $scope.showEmailResetConfirmation = false;
            if ($scope.requestPasswordResetForm.$invalid) {
                return;
            }
            $scope.errorMsg = '';
            authResource.performRequestPasswordReset(email).then(function () {
                //remove the email entered
                $scope.email = '';
                $scope.showEmailResetConfirmation = true;
            }, function (reason) {
                $scope.errorMsg = reason.errorMsg;
                $scope.requestPasswordResetForm.email.$setValidity('auth', false);
            });
            $scope.requestPasswordResetForm.email.$viewChangeListeners.push(function () {
                if ($scope.requestPasswordResetForm.email.$invalid) {
                    $scope.requestPasswordResetForm.email.$setValidity('auth', true);
                }
            });
        };
        $scope.setPasswordSubmit = function (password, confirmPassword) {
            $scope.showSetPasswordConfirmation = false;
            if (password && confirmPassword && password.length > 0 && confirmPassword.length > 0) {
                $scope.setPasswordForm.password.$setValidity('auth', true);
                $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
            }
            if ($scope.setPasswordForm.$invalid) {
                return;
            }
            //TODO: All of this logic can/should be shared! We should do validation the nice way instead of all of this manual stuff, see: inviteSavePassword
            authResource.performSetPassword($scope.resetPasswordCodeInfo.resetCodeModel.userId, password, confirmPassword, $scope.resetPasswordCodeInfo.resetCodeModel.resetCode).then(function () {
                $scope.showSetPasswordConfirmation = true;
                $scope.resetComplete = true;
                //reset the values in the resetPasswordCodeInfo angular so if someone logs out the change password isn't shown again
                resetPasswordCodeInfo.resetCodeModel = null;
            }, function (reason) {
                if (reason.data && reason.data.Message) {
                    $scope.errorMsg = reason.data.Message;
                } else {
                    $scope.errorMsg = reason.errorMsg;
                }
                $scope.setPasswordForm.password.$setValidity('auth', false);
                $scope.setPasswordForm.confirmPassword.$setValidity('auth', false);
            });
            $scope.setPasswordForm.password.$viewChangeListeners.push(function () {
                if ($scope.setPasswordForm.password.$invalid) {
                    $scope.setPasswordForm.password.$setValidity('auth', true);
                }
            });
            $scope.setPasswordForm.confirmPassword.$viewChangeListeners.push(function () {
                if ($scope.setPasswordForm.confirmPassword.$invalid) {
                    $scope.setPasswordForm.confirmPassword.$setValidity('auth', true);
                }
            });
        };
        //Now, show the correct panel:
        if ($scope.resetPasswordCodeInfo.resetCodeModel) {
            $scope.showSetPassword();
        } else if ($scope.resetPasswordCodeInfo.errors.length > 0) {
            $scope.view = 'password-reset-code-expired';
        } else {
            $scope.showLogin();
        }
        init();
    });
    //used for the macro picker dialog
    angular.module('umbraco').controller('Umbraco.Dialogs.MacroPickerController', function ($scope, macroFactory, umbPropEditorHelper) {
        $scope.macros = macroFactory.all(true);
        $scope.dialogMode = 'list';
        $scope.configureMacro = function (macro) {
            $scope.dialogMode = 'configure';
            $scope.dialogData.macro = macroFactory.getMacro(macro.alias);
            //set the correct view for each item
            for (var i = 0; i < dialogData.macro.properties.length; i++) {
                dialogData.macro.properties[i].editorView = umbPropEditorHelper.getViewPath(dialogData.macro.properties[i].view);
            }
        };
    });
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Dialogs.MediaPickerController', function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.onlyImages = dialogOptions.onlyImages;
        $scope.showDetails = dialogOptions.showDetails;
        $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false;
        $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
        $scope.cropSize = dialogOptions.cropSize;
        //preload selected item
        $scope.target = undefined;
        if (dialogOptions.currentTarget) {
            $scope.target = dialogOptions.currentTarget;
        }
        $scope.acceptedMediatypes = [];
        mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) {
            $scope.acceptedMediatypes = types;
        });
        $scope.upload = function (v) {
            angular.element('.umb-file-dropzone-directive .file-select').click();
        };
        $scope.dragLeave = function (el, event) {
            $scope.activeDrag = false;
        };
        $scope.dragEnter = function (el, event) {
            $scope.activeDrag = true;
        };
        $scope.submitFolder = function (e) {
            if (e.keyCode === 13) {
                e.preventDefault();
                mediaResource.addFolder($scope.newFolderName, $scope.currentFolder.id).then(function (data) {
                    $scope.showFolderInput = false;
                    $scope.newFolderName = '';
                    //we've added a new folder so lets clear the tree cache for that specific item
                    treeService.clearCache({
                        cacheKey: '__media',
                        //this is the main media tree cache key
                        childrenOf: data.parentId
                    });
                    $scope.gotoFolder(data);
                });
            }
        };
        $scope.gotoFolder = function (folder) {
            if (!folder) {
                folder = {
                    id: -1,
                    name: 'Media',
                    icon: 'icon-folder'
                };
            }
            if (folder.id > 0) {
                entityResource.getAncestors(folder.id, 'media').then(function (anc) {
                    // anc.splice(0,1);  
                    $scope.path = _.filter(anc, function (f) {
                        return f.path.indexOf($scope.startNodeId) !== -1;
                    });
                });
                mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) {
                    $scope.acceptedMediatypes = types;
                });
            } else {
                $scope.path = [];
            }
            //mediaResource.rootMedia()
            mediaResource.getChildren(folder.id).then(function (data) {
                $scope.searchTerm = '';
                $scope.images = data.items ? data.items : [];
            });
            $scope.currentFolder = folder;
        };
        $scope.clickHandler = function (image, ev, select) {
            ev.preventDefault();
            if (image.isFolder && !select) {
                $scope.gotoFolder(image);
            } else {
                eventsService.emit('dialogs.mediaPicker.select', image);
                //we have 3 options add to collection (if multi) show details, or submit it right back to the callback
                if ($scope.multiPicker) {
                    $scope.select(image);
                    image.cssclass = $scope.dialogData.selection.indexOf(image) > -1 ? 'selected' : '';
                } else if ($scope.showDetails) {
                    $scope.target = image;
                    $scope.target.url = mediaHelper.resolveFile(image);
                } else {
                    $scope.submit(image);
                }
            }
        };
        $scope.exitDetails = function () {
            if (!$scope.currentFolder) {
                $scope.gotoFolder();
            }
            $scope.target = undefined;
        };
        $scope.onUploadComplete = function () {
            $scope.gotoFolder($scope.currentFolder);
        };
        $scope.onFilesQueue = function () {
            $scope.activeDrag = false;
        };
        //default root item
        if (!$scope.target) {
            $scope.gotoFolder({
                id: $scope.startNodeId,
                name: 'Media',
                icon: 'icon-folder'
            });
        }
    });
    //used for the member picker dialog
    angular.module('umbraco').controller('Umbraco.Dialogs.MemberGroupPickerController', function ($scope, eventsService, entityResource, searchService, $log) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        $scope.multiPicker = dialogOptions.multiPicker;
        /** Method used for selecting a node */
        function select(text, id) {
            if (dialogOptions.multiPicker) {
                $scope.select(id);
            } else {
                $scope.submit(id);
            }
        }
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            eventsService.emit('dialogs.memberGroupPicker.select', args);
            //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
            //from the server in this method.
            select(args.node.name, args.node.id);
            //toggle checked state
            args.node.selected = args.node.selected === true ? false : true;
        }
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    angular.module('umbraco').controller('Umbraco.Dialogs.RteEmbedController', function ($scope, $http, umbRequestHelper) {
        $scope.form = {};
        $scope.form.url = '';
        $scope.form.width = 360;
        $scope.form.height = 240;
        $scope.form.constrain = true;
        $scope.form.preview = '';
        $scope.form.success = false;
        $scope.form.info = '';
        $scope.form.supportsDimensions = false;
        var origWidth = 500;
        var origHeight = 300;
        $scope.showPreview = function () {
            if ($scope.form.url) {
                $scope.form.show = true;
                $scope.form.preview = '<div class="umb-loader" style="height: 10px; margin: 10px 0px;"></div>';
                $scope.form.info = '';
                $scope.form.success = false;
                $http({
                    method: 'GET',
                    url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'),
                    params: {
                        url: $scope.form.url,
                        width: $scope.form.width,
                        height: $scope.form.height
                    }
                }).success(function (data) {
                    $scope.form.preview = '';
                    switch (data.Status) {
                    case 0:
                        //not supported
                        $scope.form.info = 'Not supported';
                        break;
                    case 1:
                        //error
                        $scope.form.info = 'Could not embed media - please ensure the URL is valid';
                        break;
                    case 2:
                        $scope.form.preview = data.Markup;
                        $scope.form.supportsDimensions = data.SupportsDimensions;
                        $scope.form.success = true;
                        break;
                    }
                }).error(function () {
                    $scope.form.supportsDimensions = false;
                    $scope.form.preview = '';
                    $scope.form.info = 'Could not embed media - please ensure the URL is valid';
                });
            } else {
                $scope.form.supportsDimensions = false;
                $scope.form.preview = '';
                $scope.form.info = 'Please enter a URL';
            }
        };
        $scope.changeSize = function (type) {
            var width, height;
            if ($scope.form.constrain) {
                width = parseInt($scope.form.width, 10);
                height = parseInt($scope.form.height, 10);
                if (type == 'width') {
                    origHeight = Math.round(width / origWidth * height);
                    $scope.form.height = origHeight;
                } else {
                    origWidth = Math.round(height / origHeight * width);
                    $scope.form.width = origWidth;
                }
            }
            if ($scope.form.url != '') {
                $scope.showPreview();
            }
        };
        $scope.insert = function () {
            $scope.submit($scope.form.preview);
        };
    });
    angular.module('umbraco').controller('Umbraco.Dialogs.Template.QueryBuilderController', function ($scope, $http, dialogService) {
        $http.get('backoffice/UmbracoApi/TemplateQuery/GetAllowedProperties').then(function (response) {
            $scope.properties = response.data;
        });
        $http.get('backoffice/UmbracoApi/TemplateQuery/GetContentTypes').then(function (response) {
            $scope.contentTypes = response.data;
        });
        $http.get('backoffice/UmbracoApi/TemplateQuery/GetFilterConditions').then(function (response) {
            $scope.conditions = response.data;
        });
        $scope.query = {
            contentType: { name: 'Everything' },
            source: { name: 'My website' },
            filters: [{
                    property: undefined,
                    operator: undefined
                }],
            sort: {
                property: {
                    alias: '',
                    name: ''
                },
                direction: 'ascending'
            }
        };
        $scope.chooseSource = function (query) {
            dialogService.contentPicker({
                callback: function (data) {
                    if (data.id > 0) {
                        query.source = {
                            id: data.id,
                            name: data.name
                        };
                    } else {
                        query.source.name = 'My website';
                        delete query.source.id;
                    }
                }
            });
        };
        var throttledFunc = _.throttle(function () {
            $http.post('backoffice/UmbracoApi/TemplateQuery/PostTemplateQuery', $scope.query).then(function (response) {
                $scope.result = response.data;
            });
        }, 200);
        $scope.$watch('query', function (value) {
            throttledFunc();
        }, true);
        $scope.getPropertyOperators = function (property) {
            var conditions = _.filter($scope.conditions, function (condition) {
                var index = condition.appliesTo.indexOf(property.type);
                return index >= 0;
            });
            return conditions;
        };
        $scope.addFilter = function (query) {
            query.filters.push({});
        };
        $scope.trashFilter = function (query) {
            query.filters.splice(query, 1);
        };
        $scope.changeSortOrder = function (query) {
            if (query.sort.direction === 'ascending') {
                query.sort.direction = 'descending';
            } else {
                query.sort.direction = 'ascending';
            }
        };
        $scope.setSortProperty = function (query, property) {
            query.sort.property = property;
            if (property.type === 'datetime') {
                query.sort.direction = 'descending';
            } else {
                query.sort.direction = 'ascending';
            }
        };
    });
    angular.module('umbraco').controller('Umbraco.Dialogs.Template.SnippetController', function ($scope) {
        $scope.type = $scope.dialogOptions.type;
        $scope.section = {
            name: '',
            required: false
        };
    });
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Dialogs.TreePickerController', function ($scope, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService) {
        var tree = null;
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        $scope.section = dialogOptions.section;
        $scope.treeAlias = dialogOptions.treeAlias;
        $scope.multiPicker = dialogOptions.multiPicker;
        $scope.hideHeader = typeof dialogOptions.hideHeader === 'boolean' ? dialogOptions.hideHeader : true;
        ;
        $scope.searchInfo = {
            searchFromId: dialogOptions.startNodeId,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        //create the custom query string param for this tree
        $scope.customTreeParams = dialogOptions.startNodeId ? 'startNodeId=' + dialogOptions.startNodeId : '';
        $scope.customTreeParams += dialogOptions.customTreeParams ? '&' + dialogOptions.customTreeParams : '';
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
        var entityType = dialogOptions.entityType ? dialogOptions.entityType : 'Document';
        //min / max values
        if (dialogOptions.minNumber) {
            dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
        }
        if (dialogOptions.maxNumber) {
            dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
        }
        if (dialogOptions.section === 'member') {
            entityType = 'Member';
        } else if (dialogOptions.section === 'media') {
            entityType = 'Media';
        }
        //Configures filtering
        if (dialogOptions.filter) {
            dialogOptions.filterExclude = false;
            dialogOptions.filterAdvanced = false;
            //used advanced filtering
            if (angular.isFunction(dialogOptions.filter)) {
                dialogOptions.filterAdvanced = true;
            } else if (angular.isObject(dialogOptions.filter)) {
                dialogOptions.filterAdvanced = true;
            } else {
                if (dialogOptions.filter.startsWith('!')) {
                    dialogOptions.filterExclude = true;
                    dialogOptions.filter = dialogOptions.filter.substring(1);
                }
                //used advanced filtering
                if (dialogOptions.filter.startsWith('{')) {
                    dialogOptions.filterAdvanced = true;
                    //convert to object
                    dialogOptions.filter = angular.fromJson(dialogOptions.filter);
                }
            }
        }
        function nodeExpandedHandler(ev, args) {
            if (angular.isArray(args.children)) {
                //iterate children
                _.each(args.children, function (child) {
                    //check if any of the items are list views, if so we need to add some custom 
                    // children: A node to activate the search, any nodes that have already been 
                    // selected in the search
                    if (child.metaData.isContainer) {
                        child.hasChildren = true;
                        child.children = [{
                                level: child.level + 1,
                                hasChildren: false,
                                parent: function () {
                                    return child;
                                },
                                name: searchText,
                                metaData: { listViewNode: child },
                                cssClass: 'icon-search',
                                cssClasses: ['not-published']
                            }];
                        //add base transition classes to this node
                        child.cssClasses.push('tree-node-slide-up');
                        var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                            return i.parentId == child.id;
                        });
                        _.each(listViewResults, function (item) {
                            child.children.unshift({
                                id: item.id,
                                name: item.name,
                                cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                level: child.level + 1,
                                metaData: { isSearchResult: true },
                                hasChildren: false,
                                parent: function () {
                                    return child;
                                }
                            });
                        });
                    }
                    //now we need to look in the already selected search results and 
                    // toggle the check boxes for those ones that are listed
                    var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                        return child.id == selected.id;
                    });
                    if (exists) {
                        child.selected = true;
                    }
                });
                //check filter
                performFiltering(args.children);
            }
        }
        //gets the tree object when it loads
        function treeLoadedHandler(ev, args) {
            tree = args.tree;
        }
        //wires up selection
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if (args.node.metaData.listViewNode) {
                //check if list view 'search' node was selected
                $scope.searchInfo.showSearch = true;
                $scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
                $scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
                //add transition classes
                var listViewNode = args.node.parent();
                listViewNode.cssClasses.push('tree-node-slide-up-hide-active');
            } else if (args.node.metaData.isSearchResult) {
                //check if the item selected was a search result from a list view
                //unselect
                select(args.node.name, args.node.id);
                //remove it from the list view children
                var listView = args.node.parent();
                listView.children = _.reject(listView.children, function (child) {
                    return child.id == args.node.id;
                });
                //remove it from the custom tracked search result list
                $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                    return i.id == args.node.id;
                });
            } else {
                eventsService.emit('dialogs.treePickerController.select', args);
                if (args.node.filtered) {
                    return;
                }
                //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
                //from the server in this method.
                select(args.node.name, args.node.id);
                //toggle checked state
                args.node.selected = args.node.selected === true ? false : true;
            }
        }
        /** Method used for selecting a node */
        function select(text, id, entity) {
            //if we get the root, we just return a constructed entity, no need for server data
            if (id < 0) {
                if ($scope.multiPicker) {
                    $scope.select(id);
                } else {
                    var node = {
                        alias: null,
                        icon: 'icon-folder',
                        id: id,
                        name: text
                    };
                    $scope.submit(node);
                }
            } else {
                if ($scope.multiPicker) {
                    $scope.select(Number(id));
                } else {
                    $scope.hideSearch();
                    //if an entity has been passed in, use it
                    if (entity) {
                        $scope.submit(entity);
                    } else {
                        //otherwise we have to get it from the server
                        entityResource.getById(id, entityType).then(function (ent) {
                            $scope.submit(ent);
                        });
                    }
                }
            }
        }
        function performFiltering(nodes) {
            if (!dialogOptions.filter) {
                return;
            }
            //remove any list view search nodes from being filtered since these are special nodes that always must
            // be allowed to be clicked on
            nodes = _.filter(nodes, function (n) {
                return !angular.isObject(n.metaData.listViewNode);
            });
            if (dialogOptions.filterAdvanced) {
                //filter either based on a method or an object
                var filtered = angular.isFunction(dialogOptions.filter) ? _.filter(nodes, dialogOptions.filter) : _.where(nodes, dialogOptions.filter);
                angular.forEach(filtered, function (value, key) {
                    value.filtered = true;
                    if (dialogOptions.filterCssClass) {
                        if (!value.cssClasses) {
                            value.cssClasses = [];
                        }
                        value.cssClasses.push(dialogOptions.filterCssClass);
                    }
                });
            } else {
                var a = dialogOptions.filter.toLowerCase().replace(/\s/g, '').split(',');
                angular.forEach(nodes, function (value, key) {
                    var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
                    if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
                        value.filtered = true;
                        if (dialogOptions.filterCssClass) {
                            if (!value.cssClasses) {
                                value.cssClasses = [];
                            }
                            value.cssClasses.push(dialogOptions.filterCssClass);
                        }
                    }
                });
            }
        }
        $scope.multiSubmit = function (result) {
            entityResource.getByIds(result, entityType).then(function (ents) {
                $scope.submit(ents);
            });
        };
        /** method to select a search result */
        $scope.selectResult = function (evt, result) {
            if (result.filtered) {
                return;
            }
            result.selected = result.selected === true ? false : true;
            //since result = an entity, we'll pass it in so we don't have to go back to the server
            select(result.name, result.id, result);
            //add/remove to our custom tracked list of selected search results
            if (result.selected) {
                $scope.searchInfo.selectedSearchResults.push(result);
            } else {
                $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                    return i.id == result.id;
                });
            }
            //ensure the tree node in the tree is checked/unchecked if it already exists there
            if (tree) {
                var found = treeService.getDescendantNode(tree.root, result.id);
                if (found) {
                    found.selected = result.selected;
                }
            }
        };
        $scope.hideSearch = function () {
            //Traverse the entire displayed tree and update each node to sync with the selected search results
            if (tree) {
                //we need to ensure that any currently displayed nodes that get selected
                // from the search get updated to have a check box!
                function checkChildren(children) {
                    _.each(children, function (child) {
                        //check if the id is in the selection, if so ensure it's flagged as selected
                        var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                            return child.id == selected.id;
                        });
                        //if the curr node exists in selected search results, ensure it's checked
                        if (exists) {
                            child.selected = true;
                        }    //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
                        else if (child.metaData.isSearchResult) {
                            //if this tree node is under a list view it means that the node was added
                            // to the tree dynamically under the list view that was searched, so we actually want to remove
                            // it all together from the tree
                            var listView = child.parent();
                            listView.children = _.reject(listView.children, function (c) {
                                return c.id == child.id;
                            });
                        }
                        //check if the current node is a list view and if so, check if there's any new results
                        // that need to be added as child nodes to it based on search results selected
                        if (child.metaData.isContainer) {
                            child.cssClasses = _.reject(child.cssClasses, function (c) {
                                return c === 'tree-node-slide-up-hide-active';
                            });
                            var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                                return i.parentId == child.id;
                            });
                            _.each(listViewResults, function (item) {
                                var childExists = _.find(child.children, function (c) {
                                    return c.id == item.id;
                                });
                                if (!childExists) {
                                    var parent = child;
                                    child.children.unshift({
                                        id: item.id,
                                        name: item.name,
                                        cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                        level: child.level + 1,
                                        metaData: { isSearchResult: true },
                                        hasChildren: false,
                                        parent: function () {
                                            return parent;
                                        }
                                    });
                                }
                            });
                        }
                        //recurse
                        if (child.children && child.children.length > 0) {
                            checkChildren(child.children);
                        }
                    });
                }
                checkChildren(tree.root.children);
            }
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        $scope.onSearchResults = function (results) {
            //filter all items - this will mark an item as filtered
            performFiltering(results);
            //now actually remove all filtered items so they are not even displayed
            results = _.filter(results, function (item) {
                return !item.filtered;
            });
            $scope.searchInfo.results = results;
            //sync with the curr selected results
            _.each($scope.searchInfo.results, function (result) {
                var exists = _.find($scope.dialogData.selection, function (selectedId) {
                    return result.id == selectedId;
                });
                if (exists) {
                    result.selected = true;
                }
            });
            $scope.searchInfo.showSearch = true;
        };
        $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    angular.module('umbraco').controller('Umbraco.Dialogs.UserController', function ($scope, $location, $timeout, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper) {
        $scope.history = historyService.getCurrent();
        $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
        $scope.showPasswordFields = false;
        $scope.changePasswordButtonState = 'init';
        $scope.externalLoginProviders = externalLoginInfo.providers;
        $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
        var evts = [];
        evts.push(eventsService.on('historyService.add', function (e, args) {
            $scope.history = args.all;
        }));
        evts.push(eventsService.on('historyService.remove', function (e, args) {
            $scope.history = args.all;
        }));
        evts.push(eventsService.on('historyService.removeAll', function (e, args) {
            $scope.history = [];
        }));
        $scope.logout = function () {
            //Add event listener for when there are pending changes on an editor which means our route was not successful
            var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) {
                //one time listener, remove the event
                pendingChangeEvent();
                $scope.close();
            });
            //perform the path change, if it is successful then the promise will resolve otherwise it will fail
            $scope.close();
            $location.path('/logout');
        };
        $scope.gotoHistory = function (link) {
            $location.path(link);
            $scope.close();
        };
        //Manually update the remaining timeout seconds
        function updateTimeout() {
            $timeout(function () {
                if ($scope.remainingAuthSeconds > 0) {
                    $scope.remainingAuthSeconds--;
                    $scope.$digest();
                    //recurse
                    updateTimeout();
                }
            }, 1000, false);    // 1 second, do NOT execute a global digest
        }
        function updateUserInfo() {
            //get the user
            userService.getCurrentUser().then(function (user) {
                $scope.user = user;
                if ($scope.user) {
                    $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
                    $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1;
                    //set the timer
                    updateTimeout();
                    authResource.getCurrentUserLinkedLogins().then(function (logins) {
                        //reset all to be un-linked
                        for (var provider in $scope.externalLoginProviders) {
                            $scope.externalLoginProviders[provider].linkedProviderKey = undefined;
                        }
                        //set the linked logins
                        for (var login in logins) {
                            var found = _.find($scope.externalLoginProviders, function (i) {
                                return i.authType == login;
                            });
                            if (found) {
                                found.linkedProviderKey = logins[login];
                            }
                        }
                    });
                }
            });
        }
        $scope.unlink = function (e, loginProvider, providerKey) {
            var result = confirm('Are you sure you want to unlink this account?');
            if (!result) {
                e.preventDefault();
                return;
            }
            authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
                updateUserInfo();
            });
        };
        updateUserInfo();
        //remove all event handlers
        $scope.$on('$destroy', function () {
            for (var e = 0; e < evts.length; e++) {
                evts[e]();
            }
        });
        /* ---------- UPDATE PASSWORD ---------- */
        //create the initial model for change password property editor
        $scope.changePasswordModel = {
            alias: '_umb_password',
            view: 'changepassword',
            config: {},
            value: {}
        };
        //go get the config for the membership provider and add it to the model
        authResource.getMembershipProviderConfig().then(function (data) {
            $scope.changePasswordModel.config = data;
            //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
            //this will ensure the oldPassword is shown so they can change it
            // disable reset password functionality beacuse it does not make sense for the current user.
            $scope.changePasswordModel.config.hasPassword = true;
            $scope.changePasswordModel.config.disableToggle = true;
            $scope.changePasswordModel.config.enableReset = false;
        });
        $scope.changePassword = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                $scope.changePasswordButtonState = 'busy';
                currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) {
                    //if the password has been reset, then update our model
                    if (data.value) {
                        $scope.changePasswordModel.value.generatedPassword = data.value;
                    }
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    $scope.changePasswordButtonState = 'success';
                }, function (err) {
                    formHelper.handleError(err);
                    $scope.changePasswordButtonState = 'error';
                });
            }
        };
        $scope.togglePasswordFields = function () {
            clearPasswordFields();
            $scope.showPasswordFields = !$scope.showPasswordFields;
        };
        function clearPasswordFields() {
            $scope.changePasswordModel.value.newPassword = '';
            $scope.changePasswordModel.confirm = '';
        }
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Dialogs.LegacyDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function YsodController($scope, legacyResource, treeService, navigationService) {
        if ($scope.error && $scope.error.data && $scope.error.data.StackTrace) {
            //trim whitespace
            $scope.error.data.StackTrace = $scope.error.data.StackTrace.trim();
        }
        $scope.closeDialog = function () {
            $scope.dismiss();
        };
    }
    angular.module('umbraco').controller('Umbraco.Dialogs.YsodController', YsodController);
    (function () {
        'use strict';
        function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter) {
            var vm = this;
            var evts = [];
            vm.title = localizationService.localize('general_help');
            vm.subtitle = 'Umbraco version' + ' ' + Umbraco.Sys.ServerVariables.application.version;
            vm.section = $routeParams.section;
            vm.tree = $routeParams.tree;
            vm.sectionName = '';
            vm.customDashboard = null;
            vm.tours = [];
            vm.closeDrawer = closeDrawer;
            vm.startTour = startTour;
            vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage;
            vm.showTourButton = showTourButton;
            function startTour(tour) {
                tourService.startTour(tour);
                closeDrawer();
            }
            function oninit() {
                tourService.getGroupedTours().then(function (groupedTours) {
                    vm.tours = groupedTours;
                    getTourGroupCompletedPercentage();
                });
                // load custom help dashboard
                dashboardResource.getDashboard('user-help').then(function (dashboard) {
                    vm.customDashboard = dashboard;
                });
                if (!vm.section) {
                    vm.section = 'content';
                }
                setSectionName();
                userService.getCurrentUser().then(function (user) {
                    vm.userType = user.userType;
                    vm.userLang = user.locale;
                    vm.hasAccessToSettings = _.contains(user.allowedSections, 'settings');
                    evts.push(eventsService.on('appState.treeState.changed', function (e, args) {
                        handleSectionChange();
                    }));
                    findHelp(vm.section, vm.tree, vm.userType, vm.userLang);
                });
                // check if a tour is running - if it is open the matching group
                var currentTour = tourService.getCurrentTour();
                if (currentTour) {
                    openTourGroup(currentTour.alias);
                }
            }
            function closeDrawer() {
                appState.setDrawerState('showDrawer', false);
            }
            function handleSectionChange() {
                $timeout(function () {
                    if (vm.section !== $routeParams.section || vm.tree !== $routeParams.tree) {
                        vm.section = $routeParams.section;
                        vm.tree = $routeParams.tree;
                        setSectionName();
                        findHelp(vm.section, vm.tree, vm.userType, vm.userLang);
                    }
                });
            }
            function findHelp(section, tree, usertype, userLang) {
                if (vm.hasAccessToSettings) {
                    helpService.getContextHelpForPage(section, tree).then(function (topics) {
                        vm.topics = topics;
                    });
                }
                var rq = {};
                rq.section = vm.section;
                rq.usertype = usertype;
                rq.lang = userLang;
                if ($routeParams.url) {
                    rq.path = decodeURIComponent($routeParams.url);
                    if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                        rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                    }
                    if (rq.path.indexOf('.aspx') > 0) {
                        rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                    }
                } else {
                    rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
                }
                if (vm.hasAccessToSettings) {
                    helpService.findVideos(rq).then(function (videos) {
                        vm.videos = videos;
                    });
                }
            }
            function setSectionName() {
                // Get section name
                var languageKey = 'sections_' + vm.section;
                localizationService.localize(languageKey).then(function (value) {
                    vm.sectionName = value;
                });
            }
            function showTourButton(index, tourGroup) {
                if (index !== 0) {
                    var prevTour = tourGroup.tours[index - 1];
                    if (prevTour.completed) {
                        return true;
                    }
                } else {
                    return true;
                }
            }
            function openTourGroup(tourAlias) {
                angular.forEach(vm.tours, function (group) {
                    angular.forEach(group, function (tour) {
                        if (tour.alias === tourAlias) {
                            group.open = true;
                        }
                    });
                });
            }
            function getTourGroupCompletedPercentage() {
                // Finding out, how many tours are completed for the progress circle
                angular.forEach(vm.tours, function (group) {
                    var completedTours = 0;
                    angular.forEach(group.tours, function (tour) {
                        if (tour.completed) {
                            completedTours++;
                        }
                    });
                    group.completedPercentage = Math.round(completedTours / group.tours.length * 100);
                });
            }
            evts.push(eventsService.on('appState.tour.complete', function (event, tour) {
                tourService.getGroupedTours().then(function (groupedTours) {
                    vm.tours = groupedTours;
                    openTourGroup(tour.alias);
                    getTourGroupCompletedPercentage();
                });
            }));
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
            oninit();
        }
        angular.module('umbraco').controller('Umbraco.Drawers.Help', HelpDrawerController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.LegacyController
 * @function
 * 
 * @description
 * A controller to control the legacy iframe injection
 * 
*/
    function LegacyController($scope, $routeParams, $element) {
        var url = decodeURIComponent($routeParams.url.replace(/javascript\:/gi, ''));
        //split into path and query
        var urlParts = url.split('?');
        var extIndex = urlParts[0].lastIndexOf('.');
        var ext = extIndex === -1 ? '' : urlParts[0].substr(extIndex);
        //path cannot be a js file
        if (ext !== '.js' || ext === '') {
            //path cannot contain any of these chars
            var toClean = '*(){}[];:<>\\|\'"';
            for (var i = 0; i < toClean.length; i++) {
                var reg = new RegExp('\\' + toClean[i], 'g');
                urlParts[0] = urlParts[0].replace(reg, '');
            }
            //join cleaned path and query back together
            url = urlParts[0] + (urlParts.length === 1 ? '' : '?' + urlParts[1]);
            $scope.legacyPath = url;
        } else {
            throw 'Invalid url';
        }
    }
    angular.module('umbraco').controller('Umbraco.LegacyController', LegacyController);
    /** This controller is simply here to launch the login dialog when the route is explicitly changed to /login */
    angular.module('umbraco').controller('Umbraco.LoginController', function (eventsService, $scope, userService, $location, $rootScope) {
        userService._showLoginDialog();
        var evtOn = eventsService.on('app.ready', function (evt, data) {
            $scope.avatar = 'assets/img/application/logo.png';
            var path = '/';
            //check if there's a returnPath query string, if so redirect to it
            var locationObj = $location.search();
            if (locationObj.returnPath) {
                path = decodeURIComponent(locationObj.returnPath);
            }
            $location.url(path);
        });
        $scope.$on('$destroy', function () {
            eventsService.unsubscribe(evtOn);
        });
    });
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Notifications.ConfirmRouteChangeController', function ($scope, $location, $log, notificationsService) {
        $scope.discard = function (not) {
            not.args.listener();
            $location.search('');
            //we need to break the path up into path and query
            var parts = not.args.path.split('?');
            var query = {};
            if (parts.length > 1) {
                _.each(parts[1].split('&'), function (q) {
                    var keyVal = q.split('=');
                    query[keyVal[0]] = keyVal[1];
                });
            }
            $location.path(parts[0]).search(query);
            notificationsService.remove(not);
        };
        $scope.stay = function (not) {
            notificationsService.remove(not);
        };
    });
    angular.module('umbraco').controller('Umbraco.Notifications.ConfirmUnpublishController', function ($scope, notificationsService, eventsService) {
        $scope.confirm = function (not, action) {
            eventsService.emit('content.confirmUnpublish', action);
            notificationsService.remove(not);
        };
    });
    (function () {
        'use strict';
        function CompositionsOverlay($scope, $location, $filter) {
            var vm = this;
            vm.isSelected = isSelected;
            vm.openContentType = openContentType;
            // group the content types by their container paths
            vm.availableGroups = $filter('orderBy')(_.map(_.groupBy($scope.model.availableCompositeContentTypes, function (compositeContentType) {
                return compositeContentType.contentType.metaData.containerPath;
            }), function (group) {
                return {
                    containerPath: group[0].contentType.metaData.containerPath,
                    compositeContentTypes: group
                };
            }), function (group) {
                return group.containerPath.replace(/\//g, ' ');
            });
            function isSelected(alias) {
                if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) {
                    return true;
                }
            }
            function openContentType(contentType, section) {
                var url = (section === 'documentType' ? '/settings/documenttypes/edit/' : '/settings/mediaTypes/edit/') + contentType.id;
                $location.path(url);
            }
        }
        angular.module('umbraco').controller('Umbraco.Overlays.CompositionsOverlay', CompositionsOverlay);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.PropertyController
 * @function
 *
 * @description
 * The controller for the content type editor property dialog
 */
    (function () {
        'use strict';
        function EditorPickerOverlay($scope, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService) {
            var vm = this;
            if (!$scope.model.title) {
                $scope.model.title = localizationService.localize('defaultdialogs_selectEditor');
            }
            vm.searchTerm = '';
            vm.showTabs = false;
            vm.tabsLoaded = 0;
            vm.typesAndEditors = [];
            vm.userConfigured = [];
            vm.loading = false;
            vm.tabs = [
                {
                    active: true,
                    id: 1,
                    label: localizationService.localize('contentTypeEditor_availableEditors'),
                    alias: 'Default',
                    typesAndEditors: []
                },
                {
                    active: false,
                    id: 2,
                    label: localizationService.localize('contentTypeEditor_reuse'),
                    alias: 'Reuse',
                    userConfigured: []
                }
            ];
            vm.filterItems = filterItems;
            vm.showDetailsOverlay = showDetailsOverlay;
            vm.hideDetailsOverlay = hideDetailsOverlay;
            vm.pickEditor = pickEditor;
            vm.pickDataType = pickDataType;
            function activate() {
                getGroupedDataTypes();
                getGroupedPropertyEditors();
            }
            function getGroupedPropertyEditors() {
                vm.loading = true;
                dataTypeResource.getGroupedPropertyEditors().then(function (data) {
                    vm.tabs[0].typesAndEditors = data;
                    vm.typesAndEditors = data;
                    vm.tabsLoaded = vm.tabsLoaded + 1;
                    checkIfTabContentIsLoaded();
                });
            }
            function getGroupedDataTypes() {
                vm.loading = true;
                dataTypeResource.getGroupedDataTypes().then(function (data) {
                    vm.tabs[1].userConfigured = data;
                    vm.userConfigured = data;
                    vm.tabsLoaded = vm.tabsLoaded + 1;
                    checkIfTabContentIsLoaded();
                });
            }
            function checkIfTabContentIsLoaded() {
                if (vm.tabsLoaded === 2) {
                    vm.loading = false;
                    vm.showTabs = true;
                }
            }
            function filterItems() {
                // clear item details
                $scope.model.itemDetails = null;
                if (vm.searchTerm) {
                    vm.showFilterResult = true;
                    vm.showTabs = false;
                } else {
                    vm.showFilterResult = false;
                    vm.showTabs = true;
                }
            }
            function showDetailsOverlay(property) {
                var propertyDetails = {};
                propertyDetails.icon = property.icon;
                propertyDetails.title = property.name;
                $scope.model.itemDetails = propertyDetails;
            }
            function hideDetailsOverlay() {
                $scope.model.itemDetails = null;
            }
            function pickEditor(editor) {
                var parentId = -1;
                dataTypeResource.getScaffold(parentId).then(function (dataType) {
                    // set alias
                    dataType.selectedEditor = editor.alias;
                    // set name
                    var nameArray = [];
                    if ($scope.model.contentTypeName) {
                        nameArray.push($scope.model.contentTypeName);
                    }
                    if ($scope.model.property.label) {
                        nameArray.push($scope.model.property.label);
                    }
                    if (editor.name) {
                        nameArray.push(editor.name);
                    }
                    // make name
                    dataType.name = nameArray.join(' - ');
                    // get pre values
                    dataTypeResource.getPreValues(dataType.selectedEditor).then(function (preValues) {
                        dataType.preValues = preValues;
                        openEditorSettingsOverlay(dataType, true);
                    });
                });
            }
            function pickDataType(selectedDataType) {
                dataTypeResource.getById(selectedDataType.id).then(function (dataType) {
                    contentTypeResource.getPropertyTypeScaffold(dataType.id).then(function (propertyType) {
                        submitOverlay(dataType, propertyType, false);
                    });
                });
            }
            function openEditorSettingsOverlay(dataType, isNew) {
                vm.editorSettingsOverlay = {
                    title: localizationService.localize('contentTypeEditor_editorSettings'),
                    dataType: dataType,
                    view: 'views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html',
                    show: true,
                    submit: function (model) {
                        var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
                        dataTypeResource.save(model.dataType, preValues, isNew).then(function (newDataType) {
                            contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) {
                                submitOverlay(newDataType, propertyType, true);
                                vm.editorSettingsOverlay.show = false;
                                vm.editorSettingsOverlay = null;
                            });
                        });
                    }
                };
            }
            function submitOverlay(dataType, propertyType, isNew) {
                // update property
                $scope.model.property.config = propertyType.config;
                $scope.model.property.editor = propertyType.editor;
                $scope.model.property.view = propertyType.view;
                $scope.model.property.dataTypeId = dataType.id;
                $scope.model.property.dataTypeIcon = dataType.icon;
                $scope.model.property.dataTypeName = dataType.name;
                $scope.model.updateSameDataTypes = isNew;
                $scope.model.submit($scope.model);
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.EditorPickerOverlay', EditorPickerOverlay);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.PropertyController
 * @function
 *
 * @description
 * The controller for the content type editor property dialog
 */
    (function () {
        'use strict';
        function PropertySettingsOverlay($scope, contentTypeResource, dataTypeResource, dataTypeHelper, localizationService, userService) {
            var vm = this;
            vm.showValidationPattern = false;
            vm.focusOnPatternField = false;
            vm.focusOnMandatoryField = false;
            vm.selectedValidationType = {};
            vm.validationTypes = [
                {
                    'name': localizationService.localize('validation_validateAsEmail'),
                    'key': 'email',
                    'pattern': '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+',
                    'enableEditing': true
                },
                {
                    'name': localizationService.localize('validation_validateAsNumber'),
                    'key': 'number',
                    'pattern': '^[0-9]*$',
                    'enableEditing': true
                },
                {
                    'name': localizationService.localize('validation_validateAsUrl'),
                    'key': 'url',
                    'pattern': 'https?://[a-zA-Z0-9-.]+.[a-zA-Z]{2,}',
                    'enableEditing': true
                },
                {
                    'name': localizationService.localize('validation_enterCustomValidation'),
                    'key': 'custom',
                    'pattern': '',
                    'enableEditing': true
                }
            ];
            vm.changeValidationType = changeValidationType;
            vm.changeValidationPattern = changeValidationPattern;
            vm.openEditorPickerOverlay = openEditorPickerOverlay;
            vm.openEditorSettingsOverlay = openEditorSettingsOverlay;
            userService.getCurrentUser().then(function (user) {
                vm.showSensitiveData = user.userGroups.indexOf('sensitiveData') != -1;
            });
            function activate() {
                matchValidationType();
            }
            function changeValidationPattern() {
                matchValidationType();
            }
            function openEditorPickerOverlay(property) {
                vm.focusOnMandatoryField = false;
                vm.editorPickerOverlay = {};
                vm.editorPickerOverlay.property = $scope.model.property;
                vm.editorPickerOverlay.contentTypeName = $scope.model.contentTypeName;
                vm.editorPickerOverlay.view = 'views/common/overlays/contenttypeeditor/editorpicker/editorpicker.html';
                vm.editorPickerOverlay.show = true;
                vm.editorPickerOverlay.submit = function (model) {
                    $scope.model.updateSameDataTypes = model.updateSameDataTypes;
                    vm.focusOnMandatoryField = true;
                    // update property
                    property.config = model.property.config;
                    property.editor = model.property.editor;
                    property.view = model.property.view;
                    property.dataTypeId = model.property.dataTypeId;
                    property.dataTypeIcon = model.property.dataTypeIcon;
                    property.dataTypeName = model.property.dataTypeName;
                    vm.editorPickerOverlay.show = false;
                    vm.editorPickerOverlay = null;
                };
                vm.editorPickerOverlay.close = function (model) {
                    vm.editorPickerOverlay.show = false;
                    vm.editorPickerOverlay = null;
                };
            }
            function openEditorSettingsOverlay(property) {
                vm.focusOnMandatoryField = false;
                // get data type
                dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                    vm.editorSettingsOverlay = {};
                    vm.editorSettingsOverlay.title = 'Editor settings';
                    vm.editorSettingsOverlay.view = 'views/common/overlays/contenttypeeditor/editorsettings/editorsettings.html';
                    vm.editorSettingsOverlay.dataType = dataType;
                    vm.editorSettingsOverlay.show = true;
                    vm.editorSettingsOverlay.submit = function (model) {
                        var preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
                        dataTypeResource.save(model.dataType, preValues, false).then(function (newDataType) {
                            contentTypeResource.getPropertyTypeScaffold(newDataType.id).then(function (propertyType) {
                                // update editor
                                property.config = propertyType.config;
                                property.editor = propertyType.editor;
                                property.view = propertyType.view;
                                property.dataTypeId = newDataType.id;
                                property.dataTypeIcon = newDataType.icon;
                                property.dataTypeName = newDataType.name;
                                // set flag to update same data types
                                $scope.model.updateSameDataTypes = true;
                                vm.focusOnMandatoryField = true;
                                vm.editorSettingsOverlay.show = false;
                                vm.editorSettingsOverlay = null;
                            });
                        });
                    };
                    vm.editorSettingsOverlay.close = function (oldModel) {
                        vm.editorSettingsOverlay.show = false;
                        vm.editorSettingsOverlay = null;
                    };
                });
            }
            function matchValidationType() {
                if ($scope.model.property.validation.pattern !== null && $scope.model.property.validation.pattern !== '' && $scope.model.property.validation.pattern !== undefined) {
                    var match = false;
                    // find and show if a match from the list has been chosen
                    angular.forEach(vm.validationTypes, function (validationType, index) {
                        if ($scope.model.property.validation.pattern === validationType.pattern) {
                            vm.selectedValidationType = vm.validationTypes[index];
                            vm.showValidationPattern = true;
                            match = true;
                        }
                    });
                    // if there is no match - choose the custom validation option.
                    if (!match) {
                        angular.forEach(vm.validationTypes, function (validationType) {
                            if (validationType.key === 'custom') {
                                vm.selectedValidationType = validationType;
                                vm.showValidationPattern = true;
                            }
                        });
                    }
                }
            }
            function changeValidationType(selectedValidationType) {
                if (selectedValidationType) {
                    $scope.model.property.validation.pattern = selectedValidationType.pattern;
                    vm.showValidationPattern = true;
                    // set focus on textarea
                    if (selectedValidationType.key === 'custom') {
                        vm.focusOnPatternField = true;
                    }
                } else {
                    $scope.model.property.validation.pattern = '';
                    vm.showValidationPattern = false;
                }
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Overlay.PropertySettingsOverlay', PropertySettingsOverlay);
    }());
    (function () {
        'use strict';
        function CopyOverlay($scope, localizationService, eventsService, entityHelper) {
            var vm = this;
            if (!$scope.model.title) {
                $scope.model.title = localizationService.localize('general_copy');
            }
            vm.hideSearch = hideSearch;
            vm.selectResult = selectResult;
            vm.onSearchResults = onSearchResults;
            var dialogOptions = $scope.model;
            var searchText = 'Search...';
            var node = dialogOptions.currentNode;
            localizationService.localize('general_search').then(function (value) {
                searchText = value + '...';
            });
            $scope.model.relateToOriginal = true;
            $scope.dialogTreeEventHandler = $({});
            vm.searchInfo = {
                searchFromId: null,
                searchFromName: null,
                showSearch: false,
                results: [],
                selectedSearchResults: []
            };
            // get entity type based on the section
            $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
            function nodeSelectHandler(ev, args) {
                if (args && args.event) {
                    args.event.preventDefault();
                    args.event.stopPropagation();
                }
                //eventsService.emit("editors.content.copyController.select", args);
                if ($scope.model.target) {
                    //un-select if there's a current one selected
                    $scope.model.target.selected = false;
                }
                $scope.model.target = args.node;
                $scope.model.target.selected = true;
            }
            function nodeExpandedHandler(ev, args) {
                // open mini list view for list views
                if (args.node.metaData.isContainer) {
                    openMiniListView(args.node);
                }
            }
            function hideSearch() {
                vm.searchInfo.showSearch = false;
                vm.searchInfo.searchFromId = null;
                vm.searchInfo.searchFromName = null;
                vm.searchInfo.results = [];
            }
            // method to select a search result
            function selectResult(evt, result) {
                result.selected = result.selected === true ? false : true;
                nodeSelectHandler(evt, {
                    event: evt,
                    node: result
                });
            }
            //callback when there are search results
            function onSearchResults(results) {
                vm.searchInfo.results = results;
                vm.searchInfo.showSearch = true;
            }
            $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
            $scope.$on('$destroy', function () {
                $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
                $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
            });
            // Mini list view
            $scope.selectListViewNode = function (node) {
                node.selected = node.selected === true ? false : true;
                nodeSelectHandler({}, { node: node });
            };
            $scope.closeMiniListView = function () {
                $scope.miniListView = undefined;
            };
            function openMiniListView(node) {
                $scope.miniListView = node;
            }
        }
        angular.module('umbraco').controller('Umbraco.Overlays.CopyOverlay', CopyOverlay);
    }());
    (function () {
        'use strict';
        function EmbedOverlay($scope, $http, umbRequestHelper, localizationService) {
            var vm = this;
            var origWidth = 500;
            var origHeight = 300;
            if (!$scope.model.title) {
                $scope.model.title = localizationService.localize('general_embed');
            }
            $scope.model.embed = {
                url: '',
                width: 360,
                height: 240,
                constrain: true,
                preview: '',
                success: false,
                info: '',
                supportsDimensions: ''
            };
            vm.showPreview = showPreview;
            vm.changeSize = changeSize;
            function showPreview() {
                if ($scope.model.embed.url) {
                    $scope.model.embed.show = true;
                    $scope.model.embed.preview = '<div class="umb-loader" style="height: 10px; margin: 10px 0px;"></div>';
                    $scope.model.embed.info = '';
                    $scope.model.embed.success = false;
                    $http({
                        method: 'GET',
                        url: umbRequestHelper.getApiUrl('embedApiBaseUrl', 'GetEmbed'),
                        params: {
                            url: $scope.model.embed.url,
                            width: $scope.model.embed.width,
                            height: $scope.model.embed.height
                        }
                    }).success(function (data) {
                        $scope.model.embed.preview = '';
                        switch (data.Status) {
                        case 0:
                            //not supported
                            $scope.model.embed.info = 'Not supported';
                            break;
                        case 1:
                            //error
                            $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid';
                            break;
                        case 2:
                            $scope.model.embed.preview = data.Markup;
                            $scope.model.embed.supportsDimensions = data.SupportsDimensions;
                            $scope.model.embed.success = true;
                            break;
                        }
                    }).error(function () {
                        $scope.model.embed.supportsDimensions = false;
                        $scope.model.embed.preview = '';
                        $scope.model.embed.info = 'Could not embed media - please ensure the URL is valid';
                    });
                } else {
                    $scope.model.embed.supportsDimensions = false;
                    $scope.model.embed.preview = '';
                    $scope.model.embed.info = 'Please enter a URL';
                }
            }
            function changeSize(type) {
                var width, height;
                if ($scope.model.embed.constrain) {
                    width = parseInt($scope.model.embed.width, 10);
                    height = parseInt($scope.model.embed.height, 10);
                    if (type == 'width') {
                        origHeight = Math.round(width / origWidth * height);
                        $scope.model.embed.height = origHeight;
                    } else {
                        origWidth = Math.round(height / origHeight * width);
                        $scope.model.embed.width = origWidth;
                    }
                }
                if ($scope.model.embed.url !== '') {
                    showPreview();
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Overlays.EmbedOverlay', EmbedOverlay);
    }());
    angular.module('umbraco').controller('Umbraco.Overlays.HelpController', function ($scope, $location, $routeParams, helpService, userService, localizationService) {
        $scope.section = $routeParams.section;
        $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
        $scope.model.subtitle = 'Umbraco version' + ' ' + $scope.version;
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('general_help');
        }
        if (!$scope.section) {
            $scope.section = 'content';
        }
        $scope.sectionName = $scope.section;
        var rq = {};
        rq.section = $scope.section;
        //translate section name
        localizationService.localize('sections_' + rq.section).then(function (value) {
            $scope.sectionName = value;
        });
        userService.getCurrentUser().then(function (user) {
            rq.lang = user.locale;
            if ($routeParams.url) {
                rq.path = decodeURIComponent($routeParams.url);
                if (rq.path.indexOf(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath) === 0) {
                    rq.path = rq.path.substring(Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath.length);
                }
                if (rq.path.indexOf('.aspx') > 0) {
                    rq.path = rq.path.substring(0, rq.path.indexOf('.aspx'));
                }
            } else {
                rq.path = rq.section + '/' + $routeParams.tree + '/' + $routeParams.method;
            }
            helpService.findHelp(rq).then(function (topics) {
                $scope.topics = topics;
            });
            helpService.findVideos(rq).then(function (videos) {
                $scope.videos = videos;
            });
        });
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.PropertyController
 * @function
 *
 * @description
 * The controller for the content type editor property dialog
 */
    function IconPickerOverlay($scope, iconHelper, localizationService) {
        $scope.loading = true;
        $scope.model.hideSubmitButton = false;
        $scope.colors = [
            {
                label: 'Black',
                value: 'color-black'
            },
            {
                label: 'Blue Grey',
                value: 'color-blue-grey'
            },
            {
                label: 'Grey',
                value: 'color-grey'
            },
            {
                label: 'Brown',
                value: 'color-brown'
            },
            {
                label: 'Blue',
                value: 'color-blue'
            },
            {
                label: 'Light Blue',
                value: 'color-light-blue'
            },
            {
                label: 'Indigo',
                value: 'color-indigo'
            },
            {
                label: 'Purple',
                value: 'color-purple'
            },
            {
                label: 'Deep Purple',
                value: 'color-deep-purple'
            },
            {
                label: 'Cyan',
                value: 'color-cyan'
            },
            {
                label: 'Green',
                value: 'color-green'
            },
            {
                label: 'Light Green',
                value: 'color-light-green'
            },
            {
                label: 'Lime',
                value: 'color-lime'
            },
            {
                label: 'Yellow',
                value: 'color-yellow'
            },
            {
                label: 'Amber',
                value: 'color-amber'
            },
            {
                label: 'Orange',
                value: 'color-orange'
            },
            {
                label: 'Deep Orange',
                value: 'color-deep-orange'
            },
            {
                label: 'Red',
                value: 'color-red'
            },
            {
                label: 'Pink',
                value: 'color-pink'
            }
        ];
        if (!$scope.color) {
            // Set default selected color to black
            $scope.color = $scope.colors[0].value;
        }
        ;
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('defaultdialogs_selectIcon');
        }
        ;
        if ($scope.model.color) {
            $scope.color = $scope.model.color;
        }
        ;
        if ($scope.model.icon) {
            $scope.icon = $scope.model.icon;
        }
        ;
        iconHelper.getIcons().then(function (icons) {
            $scope.icons = icons;
            $scope.loading = false;
        });
        $scope.selectIcon = function (icon, color) {
            $scope.model.icon = icon;
            $scope.model.color = color;
            $scope.submitForm($scope.model);
        };
        var unsubscribe = $scope.$on('formSubmitting', function () {
            if ($scope.color) {
                $scope.model.color = $scope.color;
            }
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }
    angular.module('umbraco').controller('Umbraco.Overlays.IconPickerOverlay', IconPickerOverlay);
    (function () {
        'use strict';
        function InsertOverlayController($scope, localizationService) {
            var vm = this;
            if (!$scope.model.title) {
                $scope.model.title = localizationService.localize('template_insert');
            }
            if (!$scope.model.subtitle) {
                $scope.model.subtitle = localizationService.localize('template_insertDesc');
            }
            vm.openMacroPicker = openMacroPicker;
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openPartialOverlay = openPartialOverlay;
            function openMacroPicker() {
                vm.macroPickerOverlay = {
                    view: 'macropicker',
                    title: localizationService.localize('template_insertMacro'),
                    dialogData: {},
                    show: true,
                    submit: function (model) {
                        $scope.model.insert = {
                            'type': 'macro',
                            'macroParams': model.macroParams,
                            'selectedMacro': model.selectedMacro
                        };
                        $scope.model.submit($scope.model);
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                    }
                };
            }
            function openPageFieldOverlay() {
                vm.pageFieldOverlay = {
                    title: localizationService.localize('template_insertPageField'),
                    description: localizationService.localize('template_insertPageFieldDesc'),
                    submitButtonLabel: 'Insert',
                    closeButtonlabel: 'Cancel',
                    view: 'insertfield',
                    show: true,
                    submit: function (model) {
                        $scope.model.insert = {
                            'type': 'umbracoField',
                            'umbracoField': model.umbracoField
                        };
                        $scope.model.submit($scope.model);
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                    },
                    close: function (model) {
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                    }
                };
            }
            function openDictionaryItemOverlay() {
                vm.dictionaryItemOverlay = {
                    view: 'treepicker',
                    section: 'settings',
                    treeAlias: 'dictionary',
                    entityType: 'dictionary',
                    multiPicker: false,
                    title: localizationService.localize('template_insertDictionaryItem'),
                    description: localizationService.localize('template_insertDictionaryItemDesc'),
                    emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                    show: true,
                    select: function (node) {
                        $scope.model.insert = {
                            'type': 'dictionary',
                            'node': node
                        };
                        $scope.model.submit($scope.model);
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                    },
                    close: function (model) {
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                    }
                };
            }
            function openPartialOverlay() {
                vm.partialItemOverlay = {
                    view: 'treepicker',
                    section: 'settings',
                    treeAlias: 'partialViews',
                    entityType: 'partialView',
                    multiPicker: false,
                    filter: function (i) {
                        if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                            return true;
                        }
                    },
                    filterCssClass: 'not-allowed',
                    title: localizationService.localize('template_insertPartialView'),
                    show: true,
                    select: function (node) {
                        $scope.model.insert = {
                            'type': 'partial',
                            'node': node
                        };
                        $scope.model.submit($scope.model);
                        vm.partialItemOverlay.show = false;
                        vm.partialItemOverlay = null;
                    },
                    close: function (model) {
                        vm.partialItemOverlay.show = false;
                        vm.partialItemOverlay = null;
                    }
                };
            }
        }
        angular.module('umbraco').controller('Umbraco.Overlays.InsertOverlay', InsertOverlayController);
    }());
    (function () {
        'use strict';
        function InsertFieldController($scope, $http, contentTypeResource) {
            var vm = this;
            vm.field;
            vm.altField;
            vm.altText;
            vm.insertBefore;
            vm.insertAfter;
            vm.recursive = false;
            vm.properties = [];
            vm.standardFields = [];
            vm.date = false;
            vm.dateTime = false;
            vm.dateTimeSeparator = '';
            vm.casingUpper = false;
            vm.casingLower = false;
            vm.encodeHtml = false;
            vm.encodeUrl = false;
            vm.convertLinebreaks = false;
            vm.showAltField = false;
            vm.showAltText = false;
            vm.setDateOption = setDateOption;
            vm.setCasingOption = setCasingOption;
            vm.setEncodingOption = setEncodingOption;
            vm.generateOutputSample = generateOutputSample;
            function onInit() {
                // set default title
                if (!$scope.model.title) {
                    $scope.model.title = 'Insert value';
                }
                // Load all fields
                contentTypeResource.getAllPropertyTypeAliases().then(function (array) {
                    vm.properties = array;
                });
                // Load all standard fields
                contentTypeResource.getAllStandardFields().then(function (array) {
                    vm.standardFields = array;
                });
            }
            // date formatting
            function setDateOption(option) {
                if (option === 'date') {
                    if (vm.date) {
                        vm.date = false;
                    } else {
                        vm.date = true;
                        vm.dateTime = false;
                    }
                }
                if (option === 'dateWithTime') {
                    if (vm.dateTime) {
                        vm.dateTime = false;
                    } else {
                        vm.date = false;
                        vm.dateTime = true;
                    }
                }
            }
            // casing formatting
            function setCasingOption(option) {
                if (option === 'uppercase') {
                    if (vm.casingUpper) {
                        vm.casingUpper = false;
                    } else {
                        vm.casingUpper = true;
                        vm.casingLower = false;
                    }
                }
                if (option === 'lowercase') {
                    if (vm.casingLower) {
                        vm.casingLower = false;
                    } else {
                        vm.casingUpper = false;
                        vm.casingLower = true;
                    }
                }
            }
            // encoding formatting
            function setEncodingOption(option) {
                if (option === 'html') {
                    if (vm.encodeHtml) {
                        vm.encodeHtml = false;
                    } else {
                        vm.encodeHtml = true;
                        vm.encodeUrl = false;
                    }
                }
                if (option === 'url') {
                    if (vm.encodeUrl) {
                        vm.encodeUrl = false;
                    } else {
                        vm.encodeHtml = false;
                        vm.encodeUrl = true;
                    }
                }
            }
            function generateOutputSample() {
                var pageField = (vm.field !== undefined ? '@Umbraco.Field("' + vm.field + '"' : '') + (vm.altField !== undefined ? ', altFieldAlias:"' + vm.altField + '"' : '') + (vm.altText !== undefined ? ', altText:"' + vm.altText + '"' : '') + (vm.insertBefore !== undefined ? ', insertBefore:"' + vm.insertBefore + '"' : '') + (vm.insertAfter !== undefined ? ', insertAfter:"' + vm.insertAfter + '"' : '') + (vm.recursive !== false ? ', recursive: ' + vm.recursive : '') + (vm.date !== false ? ', formatAsDate: ' + vm.date : '') + (vm.dateTime !== false ? ', formatAsDateWithTimeSeparator:"' + vm.dateTimeSeparator + '"' : '') + (vm.casingUpper !== false ? ', casing: ' + 'RenderFieldCaseType.Upper' : '') + (vm.casingLower !== false ? ', casing: ' + 'RenderFieldCaseType.Lower' : '') + (vm.encodeHtml !== false ? ', encoding: ' + 'RenderFieldEncodingType.Html' : '') + (vm.encodeUrl !== false ? ', encoding: ' + 'RenderFieldEncodingType.Url' : '') + (vm.convertLinebreaks !== false ? ', convertLineBreaks: ' + 'true' : '') + (vm.field ? ')' : '');
                $scope.model.umbracoField = pageField;
                return pageField;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.InsertFieldController', InsertFieldController);
    }());
    function ItemPickerOverlay($scope, localizationService) {
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('defaultdialogs_selectItem');
        }
        $scope.model.hideSubmitButton = true;
        $scope.selectItem = function (item) {
            $scope.model.selectedItem = item;
            $scope.submitForm($scope.model);
        };
    }
    angular.module('umbraco').controller('Umbraco.Overlays.ItemPickerOverlay', ItemPickerOverlay);
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Overlays.LinkPickerController', function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
        var dialogOptions = $scope.model;
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('defaultdialogs_selectLink');
        }
        $scope.dialogTreeEventHandler = $({});
        $scope.model.target = {};
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.showTarget = $scope.model.hideTarget !== true;
        if (dialogOptions.currentTarget) {
            // clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target
            $scope.model.target = angular.copy(dialogOptions.currentTarget);
            //if we have a node ID, we fetch the current node to build the form data
            if ($scope.model.target.id || $scope.model.target.udi) {
                //will be either a udi or an int
                var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
                // is it a content link?
                if (!$scope.model.target.isMedia) {
                    // get the content path
                    entityResource.getPath(id, 'Document').then(function (path) {
                        //now sync the tree to this path
                        $scope.dialogTreeEventHandler.syncTree({
                            path: path,
                            tree: 'content'
                        });
                    });
                    // get the content properties to build the anchor name list
                    contentResource.getById(id).then(function (resp) {
                        $scope.model.target.url = resp.urls[0];
                        $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
                    });
                }
            } else if ($scope.model.target.url.length) {
                // a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
                // only do the substring if there's a # or a ?
                var indexOfAnchor = $scope.model.target.url.search(/(#|\?)/);
                if (indexOfAnchor > -1) {
                    // populate the anchor
                    $scope.model.target.anchor = $scope.model.target.url.substring(indexOfAnchor);
                    // then rewrite the model and populate the link
                    $scope.model.target.url = $scope.model.target.url.substring(0, indexOfAnchor);
                }
            }
        } else if (dialogOptions.anchors) {
            $scope.anchorValues = dialogOptions.anchors;
        }
        function nodeSelectHandler(ev, args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('dialogs.linkPicker.select', args);
            if ($scope.currentNode) {
                //un-select if there's a current one selected
                $scope.currentNode.selected = false;
            }
            $scope.currentNode = args.node;
            $scope.currentNode.selected = true;
            $scope.model.target.id = args.node.id;
            $scope.model.target.udi = args.node.udi;
            $scope.model.target.name = args.node.name;
            if (args.node.id < 0) {
                $scope.model.target.url = '/';
            } else {
                contentResource.getById(args.node.id).then(function (resp) {
                    $scope.model.target.url = resp.urls[0];
                    $scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
                });
            }
            if (!angular.isUndefined($scope.model.target.isMedia)) {
                delete $scope.model.target.isMedia;
            }
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.switchToMediaPicker = function () {
            userService.getCurrentUser().then(function (userData) {
                $scope.mediaPickerOverlay = {
                    view: 'mediapicker',
                    startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
                    startNodeIsVirtual: userData.startMediaIds.length !== 1,
                    show: true,
                    submit: function (model) {
                        var media = model.selectedImages[0];
                        $scope.model.target.id = media.id;
                        $scope.model.target.udi = media.udi;
                        $scope.model.target.isMedia = true;
                        $scope.model.target.name = media.name;
                        $scope.model.target.url = mediaHelper.resolveFile(media);
                        $scope.mediaPickerOverlay.show = false;
                        $scope.mediaPickerOverlay = null;
                        // make sure the content tree has nothing highlighted 
                        $scope.dialogTreeEventHandler.syncTree({
                            path: '-1',
                            tree: 'content'
                        });
                    }
                };
            });
        };
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({}, { node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    function MacroPickerController($scope, entityResource, macroResource, umbPropEditorHelper, macroService, formHelper, localizationService) {
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('defaultdialogs_selectMacro');
        }
        $scope.macros = [];
        $scope.model.selectedMacro = null;
        $scope.model.macroParams = [];
        $scope.wizardStep = 'macroSelect';
        $scope.noMacroParams = false;
        $scope.selectMacro = function (macro) {
            $scope.model.selectedMacro = macro;
            if ($scope.wizardStep === 'macroSelect') {
                editParams(true);
            } else {
                $scope.model.submit($scope.model);
            }
        };
        /** changes the view to edit the params of the selected macro */
        /** if there is pnly one macro, and it has parameters - editor can skip selecting the Macro **/
        function editParams(insertIfNoParameters) {
            //whether to insert the macro in the rich text editor when editParams is called and there are no parameters see U4-10537 
            insertIfNoParameters = typeof insertIfNoParameters !== 'undefined' ? insertIfNoParameters : true;
            //get the macro params if there are any
            macroResource.getMacroParameters($scope.model.selectedMacro.id).then(function (data) {
                //go to next page if there are params otherwise we can just exit
                if (!angular.isArray(data) || data.length === 0) {
                    if (insertIfNoParameters) {
                        $scope.model.submit($scope.model);
                    } else {
                        $scope.wizardStep = 'macroSelect';
                    }
                } else {
                    $scope.wizardStep = 'paramSelect';
                    $scope.model.macroParams = data;
                    //fill in the data if we are editing this macro
                    if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroParamsDictionary) {
                        _.each($scope.model.dialogData.macroData.macroParamsDictionary, function (val, key) {
                            var prop = _.find($scope.model.macroParams, function (item) {
                                return item.alias == key;
                            });
                            if (prop) {
                                if (_.isString(val)) {
                                    //we need to unescape values as they have most likely been escaped while inserted
                                    val = _.unescape(val);
                                    //detect if it is a json string
                                    if (val.detectIsJson()) {
                                        try {
                                            //Parse it to json
                                            prop.value = angular.fromJson(val);
                                        } catch (e) {
                                            // not json
                                            prop.value = val;
                                        }
                                    } else {
                                        prop.value = val;
                                    }
                                } else {
                                    prop.value = val;
                                }
                            }
                        });
                    }
                }
            });
        }
        //here we check to see if we've been passed a selected macro and if so we'll set the
        //editor to start with parameter editing
        if ($scope.model.dialogData && $scope.model.dialogData.macroData) {
            $scope.wizardStep = 'paramSelect';
        }
        //get the macro list - pass in a filter if it is only for rte
        entityResource.getAll('Macro', $scope.model.dialogData && $scope.model.dialogData.richTextEditor && $scope.model.dialogData.richTextEditor === true ? 'UseInEditor=true' : null).then(function (data) {
            if (angular.isArray(data) && data.length == 0) {
                $scope.nomacros = true;
            }
            //if 'allowedMacros' is specified, we need to filter
            if (angular.isArray($scope.model.dialogData.allowedMacros) && $scope.model.dialogData.allowedMacros.length > 0) {
                $scope.macros = _.filter(data, function (d) {
                    return _.contains($scope.model.dialogData.allowedMacros, d.alias);
                });
            } else {
                $scope.macros = data;
            }
            //check if there's a pre-selected macro and if it exists
            if ($scope.model.dialogData && $scope.model.dialogData.macroData && $scope.model.dialogData.macroData.macroAlias) {
                var found = _.find(data, function (item) {
                    return item.alias === $scope.model.dialogData.macroData.macroAlias;
                });
                if (found) {
                    //select the macro and go to next screen
                    $scope.model.selectedMacro = found;
                    editParams(true);
                    return;
                }
            }
            //if there is only one macro in the site and it has parameters, let's not make the editor choose it from a selection of one macro (unless there are no parameters - then weirdly it's a better experience to make that selection)
            if ($scope.macros.length == 1) {
                $scope.model.selectedMacro = $scope.macros[0];
                editParams(false);
            } else {
                //we don't have a pre-selected macro so ensure the correct step is set
                $scope.wizardStep = 'macroSelect';
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Overlays.MacroPickerController', MacroPickerController);
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Overlays.MediaPickerController', function ($scope, mediaResource, umbRequestHelper, entityResource, $log, mediaHelper, mediaTypeHelper, eventsService, treeService, $element, $timeout, $cookies, localStorageService, localizationService) {
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('defaultdialogs_selectMedia');
        }
        var dialogOptions = $scope.model;
        $scope.disableFolderSelect = dialogOptions.disableFolderSelect;
        $scope.onlyImages = dialogOptions.onlyImages;
        $scope.showDetails = dialogOptions.showDetails;
        $scope.multiPicker = dialogOptions.multiPicker && dialogOptions.multiPicker !== '0' ? true : false;
        $scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
        $scope.cropSize = dialogOptions.cropSize;
        $scope.lastOpenedNode = localStorageService.get('umbLastOpenedMediaNodeId');
        $scope.lockedFolder = true;
        var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
        var allowedUploadFiles = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
        if ($scope.onlyImages) {
            $scope.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.imageFileTypes);
        } else {
            // Use whitelist of allowed file types if provided
            if (allowedUploadFiles !== '') {
                $scope.acceptedFileTypes = allowedUploadFiles;
            } else {
                // If no whitelist, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                $scope.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
            }
        }
        $scope.maxFileSize = umbracoSettings.maxFileSize + 'KB';
        $scope.model.selectedImages = [];
        $scope.acceptedMediatypes = [];
        mediaTypeHelper.getAllowedImagetypes($scope.startNodeId).then(function (types) {
            $scope.acceptedMediatypes = types;
        });
        $scope.searchOptions = {
            pageNumber: 1,
            pageSize: 100,
            totalItems: 0,
            totalPages: 0,
            filter: ''
        };
        //preload selected item
        $scope.target = undefined;
        if (dialogOptions.currentTarget) {
            $scope.target = dialogOptions.currentTarget;
        }
        function onInit() {
            if ($scope.startNodeId !== -1) {
                entityResource.getById($scope.startNodeId, 'media').then(function (ent) {
                    $scope.startNodeId = ent.id;
                    run();
                });
            } else {
                run();
            }
        }
        function run() {
            //default root item
            if (!$scope.target) {
                if ($scope.lastOpenedNode && $scope.lastOpenedNode !== -1) {
                    entityResource.getById($scope.lastOpenedNode, 'media').then(ensureWithinStartNode, gotoStartNode);
                } else {
                    gotoStartNode();
                }
            } else {
                //if a target is specified, go look it up - generally this target will just contain ids not the actual full
                //media object so we need to look it up
                var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
                var altText = $scope.target.altText;
                if (id) {
                    mediaResource.getById(id).then(function (node) {
                        $scope.target = node;
                        if (ensureWithinStartNode(node)) {
                            selectImage(node);
                            $scope.target.url = mediaHelper.resolveFile(node);
                            $scope.target.altText = altText;
                            $scope.openDetailsDialog();
                        }
                    }, gotoStartNode);
                } else {
                    gotoStartNode();
                }
            }
        }
        $scope.upload = function (v) {
            angular.element('.umb-file-dropzone-directive .file-select').click();
        };
        $scope.dragLeave = function (el, event) {
            $scope.activeDrag = false;
        };
        $scope.dragEnter = function (el, event) {
            $scope.activeDrag = true;
        };
        $scope.submitFolder = function () {
            if ($scope.newFolderName) {
                $scope.creatingFolder = true;
                mediaResource.addFolder($scope.newFolderName, $scope.currentFolder.id).then(function (data) {
                    //we've added a new folder so lets clear the tree cache for that specific item
                    treeService.clearCache({
                        cacheKey: '__media',
                        //this is the main media tree cache key
                        childrenOf: data.parentId
                    });
                    $scope.creatingFolder = false;
                    $scope.gotoFolder(data);
                    $scope.showFolderInput = false;
                    $scope.newFolderName = '';
                });
            } else {
                $scope.showFolderInput = false;
            }
        };
        $scope.enterSubmitFolder = function (event) {
            if (event.keyCode === 13) {
                $scope.submitFolder();
                event.stopPropagation();
            }
        };
        $scope.gotoFolder = function (folder) {
            if (!$scope.multiPicker) {
                deselectAllImages($scope.model.selectedImages);
            }
            if (!folder) {
                folder = {
                    id: -1,
                    name: 'Media',
                    icon: 'icon-folder'
                };
            }
            if (folder.id > 0) {
                entityResource.getAncestors(folder.id, 'media').then(function (anc) {
                    $scope.path = _.filter(anc, function (f) {
                        return f.path.indexOf($scope.startNodeId) !== -1;
                    });
                });
            } else {
                $scope.path = [];
            }
            mediaTypeHelper.getAllowedImagetypes(folder.id).then(function (types) {
                $scope.acceptedMediatypes = types;
            });
            $scope.lockedFolder = folder.id === -1 && $scope.model.startNodeIsVirtual;
            $scope.currentFolder = folder;
            localStorageService.set('umbLastOpenedMediaNodeId', folder.id);
            return getChildren(folder.id);
        };
        $scope.clickHandler = function (image, event, index) {
            if (image.isFolder) {
                if ($scope.disableFolderSelect) {
                    $scope.gotoFolder(image);
                } else {
                    eventsService.emit('dialogs.mediaPicker.select', image);
                    selectImage(image);
                }
            } else {
                eventsService.emit('dialogs.mediaPicker.select', image);
                if ($scope.showDetails) {
                    $scope.target = image;
                    // handle both entity and full media object
                    if (image.image) {
                        $scope.target.url = image.image;
                    } else {
                        $scope.target.url = mediaHelper.resolveFile(image);
                    }
                    $scope.openDetailsDialog();
                } else {
                    selectImage(image);
                }
            }
        };
        $scope.clickItemName = function (item) {
            if (item.isFolder) {
                $scope.gotoFolder(item);
            }
        };
        function selectImage(image) {
            if (image.selected) {
                for (var i = 0; $scope.model.selectedImages.length > i; i++) {
                    var imageInSelection = $scope.model.selectedImages[i];
                    if (image.key === imageInSelection.key) {
                        image.selected = false;
                        $scope.model.selectedImages.splice(i, 1);
                    }
                }
            } else {
                if (!$scope.multiPicker) {
                    deselectAllImages($scope.model.selectedImages);
                }
                image.selected = true;
                $scope.model.selectedImages.push(image);
            }
        }
        function deselectAllImages(images) {
            for (var i = 0; i < images.length; i++) {
                var image = images[i];
                image.selected = false;
            }
            images.length = 0;
        }
        $scope.onUploadComplete = function (files) {
            $scope.gotoFolder($scope.currentFolder).then(function () {
                if (files.length === 1 && $scope.model.selectedImages.length === 0) {
                    var image = $scope.images[$scope.images.length - 1];
                    $scope.target = image;
                    $scope.target.url = mediaHelper.resolveFile(image);
                    selectImage(image);
                }
            });
        };
        $scope.onFilesQueue = function () {
            $scope.activeDrag = false;
        };
        function ensureWithinStartNode(node) {
            // make sure that last opened node is on the same path as start node
            var nodePath = node.path.split(',');
            // also make sure the node is not trashed
            if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) {
                $scope.gotoFolder({
                    id: $scope.lastOpenedNode,
                    name: 'Media',
                    icon: 'icon-folder'
                });
                return true;
            } else {
                $scope.gotoFolder({
                    id: $scope.startNodeId,
                    name: 'Media',
                    icon: 'icon-folder'
                });
                return false;
            }
        }
        function gotoStartNode(err) {
            $scope.gotoFolder({
                id: $scope.startNodeId,
                name: 'Media',
                icon: 'icon-folder'
            });
        }
        $scope.openDetailsDialog = function () {
            $scope.mediaPickerDetailsOverlay = {};
            $scope.mediaPickerDetailsOverlay.show = true;
            $scope.mediaPickerDetailsOverlay.submit = function (model) {
                $scope.model.selectedImages.push($scope.target);
                $scope.model.submit($scope.model);
                $scope.mediaPickerDetailsOverlay.show = false;
                $scope.mediaPickerDetailsOverlay = null;
            };
            $scope.mediaPickerDetailsOverlay.close = function (oldModel) {
                $scope.mediaPickerDetailsOverlay.show = false;
                $scope.mediaPickerDetailsOverlay = null;
            };
        };
        var debounceSearchMedia = _.debounce(function () {
            $scope.$apply(function () {
                if ($scope.searchOptions.filter) {
                    searchMedia();
                } else {
                    // reset pagination
                    $scope.searchOptions = {
                        pageNumber: 1,
                        pageSize: 100,
                        totalItems: 0,
                        totalPages: 0,
                        filter: ''
                    };
                    getChildren($scope.currentFolder.id);
                }
            });
        }, 500);
        $scope.changeSearch = function () {
            $scope.loading = true;
            debounceSearchMedia();
        };
        $scope.toggle = function () {
            // Make sure to activate the changeSearch function everytime the toggle is clicked
            $scope.changeSearch();
        };
        $scope.changePagination = function (pageNumber) {
            $scope.loading = true;
            $scope.searchOptions.pageNumber = pageNumber;
            searchMedia();
        };
        function searchMedia() {
            $scope.loading = true;
            entityResource.getPagedDescendants($scope.currentFolder.id, 'Media', $scope.searchOptions).then(function (data) {
                // update image data to work with image grid
                angular.forEach(data.items, function (mediaItem) {
                    // set thumbnail and src
                    mediaItem.thumbnail = mediaHelper.resolveFileFromEntity(mediaItem, true);
                    mediaItem.image = mediaHelper.resolveFileFromEntity(mediaItem, false);
                    // set properties to match a media object
                    mediaItem.properties = [];
                    if (mediaItem.metaData) {
                        if (mediaItem.metaData.umbracoWidth && mediaItem.metaData.umbracoHeight) {
                            mediaItem.properties.push({
                                alias: 'umbracoWidth',
                                value: mediaItem.metaData.umbracoWidth.Value
                            });
                            mediaItem.properties.push({
                                alias: 'umbracoHeight',
                                value: mediaItem.metaData.umbracoHeight.Value
                            });
                        }
                        if (mediaItem.metaData.umbracoFile) {
                            mediaItem.properties.push({
                                alias: 'umbracoFile',
                                editor: mediaItem.metaData.umbracoFile.PropertyEditorAlias,
                                value: mediaItem.metaData.umbracoFile.Value
                            });
                        }
                    }
                });
                // update images
                $scope.images = data.items ? data.items : [];
                // update pagination
                if (data.pageNumber > 0)
                    $scope.searchOptions.pageNumber = data.pageNumber;
                if (data.pageSize > 0)
                    $scope.searchOptions.pageSize = data.pageSize;
                $scope.searchOptions.totalItems = data.totalItems;
                $scope.searchOptions.totalPages = data.totalPages;
                // set already selected images to selected
                preSelectImages();
                $scope.loading = false;
            });
        }
        function getChildren(id) {
            $scope.loading = true;
            return mediaResource.getChildren(id).then(function (data) {
                $scope.searchOptions.filter = '';
                $scope.images = data.items ? data.items : [];
                // set already selected images to selected
                preSelectImages();
                $scope.loading = false;
            });
        }
        function preSelectImages() {
            for (var folderImageIndex = 0; folderImageIndex < $scope.images.length; folderImageIndex++) {
                var folderImage = $scope.images[folderImageIndex];
                var imageIsSelected = false;
                if ($scope.model && angular.isArray($scope.model.selectedImages)) {
                    for (var selectedImageIndex = 0; selectedImageIndex < $scope.model.selectedImages.length; selectedImageIndex++) {
                        var selectedImage = $scope.model.selectedImages[selectedImageIndex];
                        if (folderImage.key === selectedImage.key) {
                            imageIsSelected = true;
                        }
                    }
                }
                if (imageIsSelected) {
                    folderImage.selected = true;
                }
            }
        }
        onInit();
    });
    angular.module('umbraco').controller('Umbraco.Overlays.MediaTypePickerController', function ($scope) {
        $scope.select = function (mediatype) {
            $scope.model.selectedType = mediatype;
            $scope.model.submit($scope.model);
            $scope.model.show = false;
        };
    });
    //used for the member picker dialog
    angular.module('umbraco').controller('Umbraco.Overlays.MemberGroupPickerController', function ($scope, eventsService, entityResource, searchService, $log, localizationService) {
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('defaultdialogs_selectMemberGroup');
        }
        $scope.dialogTreeEventHandler = $({});
        $scope.multiPicker = $scope.model.multiPicker;
        function activate() {
            if ($scope.multiPicker) {
                $scope.model.selectedMemberGroups = [];
            } else {
                $scope.model.selectedMemberGroup = '';
            }
        }
        function selectMemberGroup(id) {
            $scope.model.selectedMemberGroup = id;
        }
        function selectMemberGroups(id) {
            var index = $scope.model.selectedMemberGroups.indexOf(id);
            if (index === -1) {
                // If the id does not exists in the array then add it
                $scope.model.selectedMemberGroups.push(id);
            } else {
                // Otherwise we will remove it from the array instead
                $scope.model.selectedMemberGroups.splice(index, 1);
            }
        }
        /** Method used for selecting a node */
        function select(text, id) {
            if ($scope.model.multiPicker) {
                selectMemberGroups(id);
            } else {
                selectMemberGroup(id);
                $scope.model.submit($scope.model);
            }
        }
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            eventsService.emit('dialogs.memberGroupPicker.select', args);
            //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
            //from the server in this method.
            select(args.node.name, args.node.id);
            //toggle checked state
            args.node.selected = args.node.selected === true ? false : true;
        }
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
        activate();
    });
    (function () {
        'use strict';
        function MoveOverlay($scope, localizationService, eventsService, entityHelper) {
            var vm = this;
            vm.hideSearch = hideSearch;
            vm.selectResult = selectResult;
            vm.onSearchResults = onSearchResults;
            var dialogOptions = $scope.model;
            var searchText = 'Search...';
            var node = dialogOptions.currentNode;
            localizationService.localize('general_search').then(function (value) {
                searchText = value + '...';
            });
            if (!$scope.model.title) {
                $scope.model.title = localizationService.localize('actions_move');
            }
            $scope.model.relateToOriginal = true;
            $scope.dialogTreeEventHandler = $({});
            vm.searchInfo = {
                searchFromId: null,
                searchFromName: null,
                showSearch: false,
                results: [],
                selectedSearchResults: []
            };
            // get entity type based on the section
            $scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
            function nodeSelectHandler(ev, args) {
                if (args && args.event) {
                    args.event.preventDefault();
                    args.event.stopPropagation();
                }
                //eventsService.emit("editors.content.copyController.select", args);
                if ($scope.model.target) {
                    //un-select if there's a current one selected
                    $scope.model.target.selected = false;
                }
                $scope.model.target = args.node;
                $scope.model.target.selected = true;
            }
            function nodeExpandedHandler(ev, args) {
                // open mini list view for list views
                if (args.node.metaData.isContainer) {
                    openMiniListView(args.node);
                }
            }
            function hideSearch() {
                vm.searchInfo.showSearch = false;
                vm.searchInfo.searchFromId = null;
                vm.searchInfo.searchFromName = null;
                vm.searchInfo.results = [];
            }
            // method to select a search result
            function selectResult(evt, result) {
                result.selected = result.selected === true ? false : true;
                nodeSelectHandler(evt, {
                    event: evt,
                    node: result
                });
            }
            //callback when there are search results
            function onSearchResults(results) {
                vm.searchInfo.results = results;
                vm.searchInfo.showSearch = true;
            }
            $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
            $scope.$on('$destroy', function () {
                $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
                $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
            });
            // Mini list view
            $scope.selectListViewNode = function (node) {
                node.selected = node.selected === true ? false : true;
                nodeSelectHandler({}, { node: node });
            };
            $scope.closeMiniListView = function () {
                $scope.miniListView = undefined;
            };
            function openMiniListView(node) {
                $scope.miniListView = node;
            }
        }
        angular.module('umbraco').controller('Umbraco.Overlays.MoveOverlay', MoveOverlay);
    }());
    (function () {
        'use strict';
        function NodePermissionsController($scope, localizationService) {
            var vm = this;
            function onInit() {
                // set default title
                if (!$scope.model.title) {
                    localizationService.localize('defaultdialogs_permissionsEdit').then(function (value) {
                        $scope.model.title = value + ' ' + $scope.model.node.name;
                    });
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.NodePermissionsController', NodePermissionsController);
    }());
    (function () {
        'use strict';
        function QueryBuilderOverlayController($scope, templateQueryResource, localizationService) {
            var everything = '';
            var myWebsite = '';
            var ascendingTranslation = '';
            var descendingTranslation = '';
            var vm = this;
            vm.properties = [];
            vm.contentTypes = [];
            vm.conditions = [];
            vm.datePickerConfig = {
                pickDate: true,
                pickTime: false,
                format: 'YYYY-MM-DD'
            };
            vm.chooseSource = chooseSource;
            vm.getPropertyOperators = getPropertyOperators;
            vm.addFilter = addFilter;
            vm.trashFilter = trashFilter;
            vm.changeSortOrder = changeSortOrder;
            vm.setSortProperty = setSortProperty;
            vm.setContentType = setContentType;
            vm.setFilterProperty = setFilterProperty;
            vm.setFilterTerm = setFilterTerm;
            vm.changeConstraintValue = changeConstraintValue;
            vm.datePickerChange = datePickerChange;
            function onInit() {
                vm.query = {
                    contentType: { name: everything },
                    source: { name: myWebsite },
                    filters: [{
                            property: undefined,
                            operator: undefined
                        }],
                    sort: {
                        property: {
                            alias: '',
                            name: ''
                        },
                        direction: 'ascending',
                        //This is the value for sorting sent to server
                        translation: {
                            currentLabel: ascendingTranslation,
                            //This is the localized UI value in the the dialog
                            ascending: ascendingTranslation,
                            descending: descendingTranslation
                        }
                    }
                };
                templateQueryResource.getAllowedProperties().then(function (properties) {
                    vm.properties = properties;
                });
                templateQueryResource.getContentTypes().then(function (contentTypes) {
                    vm.contentTypes = contentTypes;
                });
                templateQueryResource.getFilterConditions().then(function (conditions) {
                    vm.conditions = conditions;
                });
                throttledFunc();
            }
            function chooseSource(query) {
                vm.contentPickerOverlay = {
                    view: 'contentpicker',
                    show: true,
                    submit: function (model) {
                        var selectedNodeId = model.selection[0].id;
                        var selectedNodeName = model.selection[0].name;
                        if (selectedNodeId > 0) {
                            query.source = {
                                id: selectedNodeId,
                                name: selectedNodeName
                            };
                        } else {
                            query.source.name = myWebsite;
                            delete query.source.id;
                        }
                        throttledFunc();
                        vm.contentPickerOverlay.show = false;
                        vm.contentPickerOverlay = null;
                    },
                    close: function (oldModel) {
                        vm.contentPickerOverlay.show = false;
                        vm.contentPickerOverlay = null;
                    }
                };
            }
            function getPropertyOperators(property) {
                var conditions = _.filter(vm.conditions, function (condition) {
                    var index = condition.appliesTo.indexOf(property.type);
                    return index >= 0;
                });
                return conditions;
            }
            function addFilter(query) {
                query.filters.push({});
            }
            function trashFilter(query, filter) {
                for (var i = 0; i < query.filters.length; i++) {
                    if (query.filters[i] == filter) {
                        query.filters.splice(i, 1);
                    }
                }
                //if we remove the last one, add a new one to generate ui for it.
                if (query.filters.length == 0) {
                    query.filters.push({});
                }
            }
            function changeSortOrder(query) {
                if (query.sort.direction === 'ascending') {
                    query.sort.direction = 'descending';
                    query.sort.translation.currentLabel = query.sort.translation.descending;
                } else {
                    query.sort.direction = 'ascending';
                    query.sort.translation.currentLabel = query.sort.translation.ascending;
                }
                throttledFunc();
            }
            function setSortProperty(query, property) {
                query.sort.property = property;
                if (property.type === 'datetime') {
                    query.sort.direction = 'descending';
                    query.sort.translation.currentLabel = query.sort.translation.descending;
                } else {
                    query.sort.direction = 'ascending';
                    query.sort.translation.currentLabel = query.sort.translation.ascending;
                }
                throttledFunc();
            }
            function setContentType(contentType) {
                vm.query.contentType = contentType;
                throttledFunc();
            }
            function setFilterProperty(filter, property) {
                filter.property = property;
                filter.term = {};
                filter.constraintValue = '';
            }
            function setFilterTerm(filter, term) {
                filter.term = term;
                if (filter.constraintValue) {
                    throttledFunc();
                }
            }
            function changeConstraintValue() {
                throttledFunc();
            }
            function datePickerChange(event, filter) {
                if (event.date && event.date.isValid()) {
                    filter.constraintValue = event.date.format(vm.datePickerConfig.format);
                    throttledFunc();
                }
            }
            var throttledFunc = _.throttle(function () {
                templateQueryResource.postTemplateQuery(vm.query).then(function (response) {
                    $scope.model.result = response;
                });
            }, 200);
            localizationService.localizeMany([
                'template_allContent',
                'template_websiteRoot',
                'template_ascending',
                'template_descending'
            ]).then(function (res) {
                everything = res[0];
                myWebsite = res[1];
                ascendingTranslation = res[2];
                descendingTranslation = res[3];
                onInit();
            });
        }
        angular.module('umbraco').controller('Umbraco.Overlays.QueryBuilderController', QueryBuilderOverlayController);
    }());
    (function () {
        'use strict';
        function SectionPickerController($scope, sectionResource, localizationService) {
            var vm = this;
            vm.sections = [];
            vm.loading = false;
            vm.selectSection = selectSection;
            //////////
            function onInit() {
                vm.loading = true;
                // set default title
                if (!$scope.model.title) {
                    $scope.model.title = localizationService.localize('defaultdialogs_selectSections');
                }
                // make sure we can push to something
                if (!$scope.model.selection) {
                    $scope.model.selection = [];
                }
                // get sections
                sectionResource.getAllSections().then(function (sections) {
                    vm.sections = sections;
                    setSectionIcon(vm.sections);
                    if ($scope.model.selection && $scope.model.selection.length > 0) {
                        preSelect($scope.model.selection);
                    }
                    vm.loading = false;
                });
            }
            function preSelect(selection) {
                angular.forEach(selection, function (selected) {
                    angular.forEach(vm.sections, function (section) {
                        if (selected.alias === section.alias) {
                            section.selected = true;
                        }
                    });
                });
            }
            function selectSection(section) {
                if (!section.selected) {
                    section.selected = true;
                    $scope.model.selection.push(section);
                } else {
                    angular.forEach($scope.model.selection, function (selectedSection, index) {
                        if (selectedSection.alias === section.alias) {
                            section.selected = false;
                            $scope.model.selection.splice(index, 1);
                        }
                    });
                }
            }
            function setSectionIcon(sections) {
                angular.forEach(sections, function (section) {
                    section.icon = 'icon-section ' + section.cssclass;
                });
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.SectionPickerController', SectionPickerController);
    }());
    (function () {
        'use strict';
        function TemplateSectionsOverlayController($scope) {
            var vm = this;
            $scope.model.mandatoryRenderSection = false;
            if (!$scope.model.title) {
                $scope.model.title = 'Sections';
            }
            vm.select = select;
            function onInit() {
                if ($scope.model.hasMaster) {
                    $scope.model.insertType = 'addSection';
                } else {
                    $scope.model.insertType = 'renderBody';
                }
            }
            function select(type) {
                $scope.model.insertType = type;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.TemplateSectionsOverlay', TemplateSectionsOverlayController);
    }());
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Overlays.TreePickerController', function ($scope, $q, entityResource, eventsService, $log, searchService, angularHelper, $timeout, localizationService, treeService, contentResource, mediaResource, memberResource) {
        var tree = null;
        var dialogOptions = $scope.model;
        $scope.treeReady = false;
        $scope.dialogTreeEventHandler = $({});
        $scope.section = dialogOptions.section;
        $scope.treeAlias = dialogOptions.treeAlias;
        $scope.multiPicker = dialogOptions.multiPicker;
        $scope.hideHeader = typeof dialogOptions.hideHeader === 'boolean' ? dialogOptions.hideHeader : true;
        // if you need to load a not initialized tree set this value to false - default is true
        $scope.onlyInitialized = dialogOptions.onlyInitialized;
        $scope.searchInfo = {
            searchFromId: dialogOptions.startNodeId,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.model.selection = [];
        //Used for toggling an empty-state message
        //Some trees can have no items (dictionary & forms email templates)
        $scope.hasItems = true;
        $scope.emptyStateMessage = dialogOptions.emptyStateMessage;
        var node = dialogOptions.currentNode;
        //This is called from ng-init
        //it turns out it is called from the angular html : / Have a look at views/common / overlays / contentpicker / contentpicker.html you'll see ng-init. 
        //this is probably an anti pattern IMO and shouldn't be used
        $scope.init = function (contentType) {
            if (contentType === 'content') {
                $scope.entityType = 'Document';
                if (!$scope.model.title) {
                    $scope.model.title = localizationService.localize('defaultdialogs_selectContent');
                }
            } else if (contentType === 'member') {
                $scope.entityType = 'Member';
                if (!$scope.model.title) {
                    $scope.model.title = localizationService.localize('defaultdialogs_selectMember');
                }
            } else if (contentType === 'media') {
                $scope.entityType = 'Media';
                if (!$scope.model.title) {
                    $scope.model.title = localizationService.localize('defaultdialogs_selectMedia');
                }
            }
        };
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        // Allow the entity type to be passed in but defaults to Document for backwards compatibility.
        $scope.entityType = dialogOptions.entityType ? dialogOptions.entityType : 'Document';
        //min / max values
        if (dialogOptions.minNumber) {
            dialogOptions.minNumber = parseInt(dialogOptions.minNumber, 10);
        }
        if (dialogOptions.maxNumber) {
            dialogOptions.maxNumber = parseInt(dialogOptions.maxNumber, 10);
        }
        if (dialogOptions.section === 'member') {
            $scope.entityType = 'Member';
        } else if (dialogOptions.section === 'media') {
            $scope.entityType = 'Media';
        }
        // Search and listviews is only working for content, media and member section
        var searchableSections = [
            'content',
            'media',
            'member'
        ];
        $scope.enableSearh = searchableSections.indexOf($scope.section) !== -1;
        //if a alternative startnode is used, we need to check if it is a container
        if ($scope.enableSearh && dialogOptions.startNodeId && dialogOptions.startNodeId !== -1 && dialogOptions.startNodeId !== '-1') {
            entityResource.getById(dialogOptions.startNodeId, $scope.entityType).then(function (node) {
                if (node.metaData.IsContainer) {
                    openMiniListView(node);
                }
                initTree();
            });
        } else {
            initTree();
        }
        //Configures filtering
        if (dialogOptions.filter) {
            dialogOptions.filterExclude = false;
            dialogOptions.filterAdvanced = false;
            //used advanced filtering
            if (angular.isFunction(dialogOptions.filter)) {
                dialogOptions.filterAdvanced = true;
            } else if (angular.isObject(dialogOptions.filter)) {
                dialogOptions.filterAdvanced = true;
            } else {
                if (dialogOptions.filter.startsWith('!')) {
                    dialogOptions.filterExclude = true;
                    dialogOptions.filterTypes = dialogOptions.filter.substring(1);
                } else {
                    dialogOptions.filterExclude = false;
                    dialogOptions.filterTypes = dialogOptions.filter;
                }
                //used advanced filtering
                if (dialogOptions.filter.startsWith('{')) {
                    dialogOptions.filterAdvanced = true;
                    //convert to object
                    dialogOptions.filter = angular.fromJson(dialogOptions.filter);
                }
            }
            $scope.filter = {
                filterAdvanced: dialogOptions.filterAdvanced,
                filterExclude: dialogOptions.filterExclude,
                filter: dialogOptions.filterTypes
            };
        }
        function initTree() {
            //create the custom query string param for this tree
            $scope.customTreeParams = dialogOptions.startNodeId ? 'startNodeId=' + dialogOptions.startNodeId : '';
            $scope.customTreeParams += dialogOptions.customTreeParams ? '&' + dialogOptions.customTreeParams : '';
            $scope.treeReady = true;
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
            if (angular.isArray(args.children)) {
                //iterate children
                _.each(args.children, function (child) {
                    //now we need to look in the already selected search results and
                    // toggle the check boxes for those ones that are listed
                    var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                        return child.id == selected.id;
                    });
                    if (exists) {
                        child.selected = true;
                    }
                });
                //check filter
                performFiltering(args.children);
            }
        }
        //gets the tree object when it loads
        function treeLoadedHandler(ev, args) {
            //args.tree contains children (args.tree.root.children)
            $scope.hasItems = args.tree.root.children.length > 0;
            tree = args.tree;
            var nodeHasPath = typeof node !== 'undefined' && typeof node.path !== 'undefined';
            var startNodeNotDefined = typeof dialogOptions.startNodeId === 'undefined' || dialogOptions.startNodeId === '' || dialogOptions.startNodeId === '-1';
            if (startNodeNotDefined && nodeHasPath) {
                $scope.dialogTreeEventHandler.syncTree({
                    path: node.path,
                    activate: false
                });
            }
        }
        //wires up selection
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if (args.node.metaData.isSearchResult) {
                //check if the item selected was a search result from a list view
                //unselect
                select(args.node.name, args.node.id);
                //remove it from the list view children
                var listView = args.node.parent();
                listView.children = _.reject(listView.children, function (child) {
                    return child.id == args.node.id;
                });
                //remove it from the custom tracked search result list
                $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                    return i.id == args.node.id;
                });
            } else {
                eventsService.emit('dialogs.treePickerController.select', args);
                if (args.node.filtered) {
                    return;
                }
                //This is a tree node, so we don't have an entity to pass in, it will need to be looked up
                //from the server in this method.
                if ($scope.model.select) {
                    $scope.model.select(args.node);
                } else {
                    select(args.node.name, args.node.id);
                    //toggle checked state
                    args.node.selected = args.node.selected === true ? false : true;
                }
            }
        }
        /** Method used for selecting a node */
        function select(text, id, entity) {
            //if we get the root, we just return a constructed entity, no need for server data
            if (id < 0) {
                var rootNode = {
                    alias: null,
                    icon: 'icon-folder',
                    id: id,
                    name: text
                };
                if ($scope.multiPicker) {
                    if (entity) {
                        multiSelectItem(entity);
                    } else {
                        multiSelectItem(rootNode);
                    }
                } else {
                    $scope.model.selection.push(rootNode);
                    $scope.model.submit($scope.model);
                }
            } else {
                if ($scope.multiPicker) {
                    if (entity) {
                        multiSelectItem(entity);
                    } else {
                        //otherwise we have to get it from the server
                        entityResource.getById(id, $scope.entityType).then(function (ent) {
                            multiSelectItem(ent);
                        });
                    }
                } else {
                    $scope.hideSearch();
                    //if an entity has been passed in, use it
                    if (entity) {
                        $scope.model.selection.push(entity);
                        $scope.model.submit($scope.model);
                    } else {
                        //otherwise we have to get it from the server
                        entityResource.getById(id, $scope.entityType).then(function (ent) {
                            $scope.model.selection.push(ent);
                            $scope.model.submit($scope.model);
                        });
                    }
                }
            }
        }
        function multiSelectItem(item) {
            var found = false;
            var foundIndex = 0;
            if ($scope.model.selection.length > 0) {
                for (i = 0; $scope.model.selection.length > i; i++) {
                    var selectedItem = $scope.model.selection[i];
                    if (selectedItem.id === item.id) {
                        found = true;
                        foundIndex = i;
                    }
                }
            }
            if (found) {
                $scope.model.selection.splice(foundIndex, 1);
            } else {
                $scope.model.selection.push(item);
            }
        }
        function performFiltering(nodes) {
            if (!dialogOptions.filter) {
                return;
            }
            //remove any list view search nodes from being filtered since these are special nodes that always must
            // be allowed to be clicked on
            nodes = _.filter(nodes, function (n) {
                return !angular.isObject(n.metaData.listViewNode);
            });
            if (dialogOptions.filterAdvanced) {
                //filter either based on a method or an object
                var filtered = angular.isFunction(dialogOptions.filter) ? _.filter(nodes, dialogOptions.filter) : _.where(nodes, dialogOptions.filter);
                angular.forEach(filtered, function (value, key) {
                    value.filtered = true;
                    if (dialogOptions.filterCssClass) {
                        if (!value.cssClasses) {
                            value.cssClasses = [];
                        }
                        value.cssClasses.push(dialogOptions.filterCssClass);
                    }
                });
            } else {
                var a = dialogOptions.filterTypes.toLowerCase().replace(/\s/g, '').split(',');
                angular.forEach(nodes, function (value, key) {
                    var found = a.indexOf(value.metaData.contentType.toLowerCase()) >= 0;
                    if (!dialogOptions.filterExclude && !found || dialogOptions.filterExclude && found) {
                        value.filtered = true;
                        if (dialogOptions.filterCssClass) {
                            if (!value.cssClasses) {
                                value.cssClasses = [];
                            }
                            value.cssClasses.push(dialogOptions.filterCssClass);
                        }
                    }
                });
            }
        }
        $scope.multiSubmit = function (result) {
            entityResource.getByIds(result, $scope.entityType).then(function (ents) {
                $scope.submit(ents);
            });
        };
        /** method to select a search result */
        $scope.selectResult = function (evt, result) {
            if (result.filtered) {
                return;
            }
            result.selected = result.selected === true ? false : true;
            //since result = an entity, we'll pass it in so we don't have to go back to the server
            select(result.name, result.id, result);
            //add/remove to our custom tracked list of selected search results
            if (result.selected) {
                $scope.searchInfo.selectedSearchResults.push(result);
            } else {
                $scope.searchInfo.selectedSearchResults = _.reject($scope.searchInfo.selectedSearchResults, function (i) {
                    return i.id == result.id;
                });
            }
            //ensure the tree node in the tree is checked/unchecked if it already exists there
            if (tree) {
                var found = treeService.getDescendantNode(tree.root, result.id);
                if (found) {
                    found.selected = result.selected;
                }
            }
        };
        $scope.hideSearch = function () {
            //Traverse the entire displayed tree and update each node to sync with the selected search results
            if (tree) {
                //we need to ensure that any currently displayed nodes that get selected
                // from the search get updated to have a check box!
                function checkChildren(children) {
                    _.each(children, function (child) {
                        //check if the id is in the selection, if so ensure it's flagged as selected
                        var exists = _.find($scope.searchInfo.selectedSearchResults, function (selected) {
                            return child.id == selected.id;
                        });
                        //if the curr node exists in selected search results, ensure it's checked
                        if (exists) {
                            child.selected = true;
                        }    //if the curr node does not exist in the selected search result, and the curr node is a child of a list view search result
                        else if (child.metaData.isSearchResult) {
                            //if this tree node is under a list view it means that the node was added
                            // to the tree dynamically under the list view that was searched, so we actually want to remove
                            // it all together from the tree
                            var listView = child.parent();
                            listView.children = _.reject(listView.children, function (c) {
                                return c.id == child.id;
                            });
                        }
                        //check if the current node is a list view and if so, check if there's any new results
                        // that need to be added as child nodes to it based on search results selected
                        if (child.metaData.isContainer) {
                            child.cssClasses = _.reject(child.cssClasses, function (c) {
                                return c === 'tree-node-slide-up-hide-active';
                            });
                            var listViewResults = _.filter($scope.searchInfo.selectedSearchResults, function (i) {
                                return i.parentId == child.id;
                            });
                            _.each(listViewResults, function (item) {
                                var childExists = _.find(child.children, function (c) {
                                    return c.id == item.id;
                                });
                                if (!childExists) {
                                    var parent = child;
                                    child.children.unshift({
                                        id: item.id,
                                        name: item.name,
                                        cssClass: 'icon umb-tree-icon sprTree ' + item.icon,
                                        level: child.level + 1,
                                        metaData: { isSearchResult: true },
                                        hasChildren: false,
                                        parent: function () {
                                            return parent;
                                        }
                                    });
                                }
                            });
                        }
                        //recurse
                        if (child.children && child.children.length > 0) {
                            checkChildren(child.children);
                        }
                    });
                }
                checkChildren(tree.root.children);
            }
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = dialogOptions.startNodeId;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        $scope.onSearchResults = function (results) {
            //filter all items - this will mark an item as filtered
            performFiltering(results);
            //now actually remove all filtered items so they are not even displayed
            results = _.filter(results, function (item) {
                return !item.filtered;
            });
            $scope.searchInfo.results = results;
            //sync with the curr selected results
            _.each($scope.searchInfo.results, function (result) {
                var exists = _.find($scope.model.selection, function (selectedId) {
                    return result.id == selectedId;
                });
                if (exists) {
                    result.selected = true;
                }
            });
            $scope.searchInfo.showSearch = true;
        };
        $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
        $scope.selectListViewNode = function (node) {
            select(node.name, node.id);
            //toggle checked state
            node.selected = node.selected === true ? false : true;
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    angular.module('umbraco').controller('Umbraco.Overlays.UserController', function ($scope, $location, $timeout, dashboardResource, userService, historyService, eventsService, externalLoginInfo, authResource, currentUserResource, formHelper, localizationService) {
        $scope.history = historyService.getCurrent();
        $scope.version = Umbraco.Sys.ServerVariables.application.version + ' assembly: ' + Umbraco.Sys.ServerVariables.application.assemblyVersion;
        $scope.showPasswordFields = false;
        $scope.changePasswordButtonState = 'init';
        $scope.model.subtitle = 'Umbraco version' + ' ' + $scope.version;
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('general_user');
        }
        $scope.externalLoginProviders = externalLoginInfo.providers;
        $scope.externalLinkLoginFormAction = Umbraco.Sys.ServerVariables.umbracoUrls.externalLinkLoginsUrl;
        var evts = [];
        evts.push(eventsService.on('historyService.add', function (e, args) {
            $scope.history = args.all;
        }));
        evts.push(eventsService.on('historyService.remove', function (e, args) {
            $scope.history = args.all;
        }));
        evts.push(eventsService.on('historyService.removeAll', function (e, args) {
            $scope.history = [];
        }));
        $scope.logout = function () {
            //Add event listener for when there are pending changes on an editor which means our route was not successful
            var pendingChangeEvent = eventsService.on('valFormManager.pendingChanges', function (e, args) {
                //one time listener, remove the event
                pendingChangeEvent();
                $scope.model.close();
            });
            //perform the path change, if it is successful then the promise will resolve otherwise it will fail
            $scope.model.close();
            $location.path('/logout');
        };
        $scope.gotoHistory = function (link) {
            $location.path(link);
            $scope.model.close();
        };
        //Manually update the remaining timeout seconds
        function updateTimeout() {
            $timeout(function () {
                if ($scope.remainingAuthSeconds > 0) {
                    $scope.remainingAuthSeconds--;
                    $scope.$digest();
                    //recurse
                    updateTimeout();
                }
            }, 1000, false);    // 1 second, do NOT execute a global digest
        }
        function updateUserInfo() {
            //get the user
            userService.getCurrentUser().then(function (user) {
                $scope.user = user;
                if ($scope.user) {
                    $scope.model.title = user.name;
                    $scope.remainingAuthSeconds = $scope.user.remainingAuthSeconds;
                    $scope.canEditProfile = _.indexOf($scope.user.allowedSections, 'users') > -1;
                    //set the timer
                    updateTimeout();
                    authResource.getCurrentUserLinkedLogins().then(function (logins) {
                        //reset all to be un-linked
                        for (var provider in $scope.externalLoginProviders) {
                            $scope.externalLoginProviders[provider].linkedProviderKey = undefined;
                        }
                        //set the linked logins
                        for (var login in logins) {
                            var found = _.find($scope.externalLoginProviders, function (i) {
                                return i.authType == login;
                            });
                            if (found) {
                                found.linkedProviderKey = logins[login];
                            }
                        }
                    });
                }
            });
        }
        $scope.unlink = function (e, loginProvider, providerKey) {
            var result = confirm('Are you sure you want to unlink this account?');
            if (!result) {
                e.preventDefault();
                return;
            }
            authResource.unlinkLogin(loginProvider, providerKey).then(function (a, b, c) {
                updateUserInfo();
            });
        };
        updateUserInfo();
        //remove all event handlers
        $scope.$on('$destroy', function () {
            for (var e = 0; e < evts.length; e++) {
                evts[e]();
            }
        });
        /* ---------- UPDATE PASSWORD ---------- */
        //create the initial model for change password
        $scope.changePasswordModel = {
            config: {},
            value: {}
        };
        //go get the config for the membership provider and add it to the model
        authResource.getMembershipProviderConfig().then(function (data) {
            $scope.changePasswordModel.config = data;
            //ensure the hasPassword config option is set to true (the user of course has a password already assigned)
            //this will ensure the oldPassword is shown so they can change it
            // disable reset password functionality beacuse it does not make sense inside the backoffice
            $scope.changePasswordModel.config.hasPassword = true;
            $scope.changePasswordModel.config.disableToggle = true;
            $scope.changePasswordModel.config.enableReset = false;
        });
        $scope.changePassword = function () {
            if (formHelper.submitForm({ scope: $scope })) {
                $scope.changePasswordButtonState = 'busy';
                currentUserResource.changePassword($scope.changePasswordModel.value).then(function (data) {
                    //reset old data 
                    clearPasswordFields();
                    //if the password has been reset, then update our model
                    if (data.value) {
                        $scope.changePasswordModel.value.generatedPassword = data.value;
                    }
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    $scope.changePasswordButtonState = 'success';
                    $timeout(function () {
                        $scope.togglePasswordFields();
                    }, 2000);
                }, function (err) {
                    formHelper.handleError(err);
                    $scope.changePasswordButtonState = 'error';
                });
            }
        };
        $scope.togglePasswordFields = function () {
            clearPasswordFields();
            $scope.showPasswordFields = !$scope.showPasswordFields;
        };
        function clearPasswordFields() {
            $scope.changePasswordModel.value.oldPassword = '';
            $scope.changePasswordModel.value.newPassword = '';
            $scope.changePasswordModel.value.confirm = '';
        }
        dashboardResource.getDashboard('user-dialog').then(function (dashboard) {
            $scope.dashboard = dashboard;
        });
    });
    (function () {
        'use strict';
        function UserGroupPickerController($scope, userGroupsResource, localizationService) {
            var vm = this;
            vm.userGroups = [];
            vm.loading = false;
            vm.selectUserGroup = selectUserGroup;
            //////////
            function onInit() {
                vm.loading = true;
                // set default title
                if (!$scope.model.title) {
                    $scope.model.title = localizationService.localize('defaultdialogs_selectUsers');
                }
                // make sure we can push to something
                if (!$scope.model.selection) {
                    $scope.model.selection = [];
                }
                // get venues
                userGroupsResource.getUserGroups().then(function (userGroups) {
                    vm.userGroups = userGroups;
                    if ($scope.model.selection && $scope.model.selection.length > 0) {
                        preSelect($scope.model.selection);
                    }
                    vm.loading = false;
                });
            }
            function preSelect(selection) {
                angular.forEach(selection, function (selected) {
                    angular.forEach(vm.userGroups, function (userGroup) {
                        if (selected.id === userGroup.id) {
                            userGroup.selected = true;
                        }
                    });
                });
            }
            function selectUserGroup(userGroup) {
                if (!userGroup.selected) {
                    userGroup.selected = true;
                    $scope.model.selection.push(userGroup);
                } else {
                    angular.forEach($scope.model.selection, function (selectedUserGroup, index) {
                        if (selectedUserGroup.id === userGroup.id) {
                            userGroup.selected = false;
                            $scope.model.selection.splice(index, 1);
                        }
                    });
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.UserGroupPickerController', UserGroupPickerController);
    }());
    (function () {
        'use strict';
        function UserPickerController($scope, usersResource, localizationService) {
            var vm = this;
            vm.users = [];
            vm.loading = false;
            vm.usersOptions = {};
            vm.selectUser = selectUser;
            vm.searchUsers = searchUsers;
            vm.changePageNumber = changePageNumber;
            //////////
            function onInit() {
                vm.loading = true;
                // set default title
                if (!$scope.model.title) {
                    $scope.model.title = localizationService.localize('defaultdialogs_selectUsers');
                }
                // make sure we can push to something
                if (!$scope.model.selection) {
                    $scope.model.selection = [];
                }
                // get users
                getUsers();
            }
            function preSelect(selection, users) {
                angular.forEach(selection, function (selected) {
                    angular.forEach(users, function (user) {
                        if (selected.id === user.id) {
                            user.selected = true;
                        }
                    });
                });
            }
            function selectUser(user) {
                if (!user.selected) {
                    user.selected = true;
                    $scope.model.selection.push(user);
                } else {
                    angular.forEach($scope.model.selection, function (selectedUser, index) {
                        if (selectedUser.id === user.id) {
                            user.selected = false;
                            $scope.model.selection.splice(index, 1);
                        }
                    });
                }
            }
            var search = _.debounce(function () {
                $scope.$apply(function () {
                    getUsers();
                });
            }, 500);
            function searchUsers() {
                search();
            }
            function getUsers() {
                vm.loading = true;
                // Get users
                usersResource.getPagedResults(vm.usersOptions).then(function (users) {
                    vm.users = users.items;
                    vm.usersOptions.pageNumber = users.pageNumber;
                    vm.usersOptions.pageSize = users.pageSize;
                    vm.usersOptions.totalItems = users.totalItems;
                    vm.usersOptions.totalPages = users.totalPages;
                    preSelect($scope.model.selection, vm.users);
                    vm.loading = false;
                });
            }
            function changePageNumber(pageNumber) {
                vm.usersOptions.pageNumber = pageNumber;
                getUsers();
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Overlays.UserPickerController', UserPickerController);
    }());
    angular.module('umbraco').controller('Umbraco.Overlays.YsodController', function ($scope, legacyResource, treeService, navigationService, localizationService) {
        if (!$scope.model.title) {
            $scope.model.title = localizationService.localize('errors_receivedErrorFromServer');
        }
        if ($scope.model.error && $scope.model.error.data && $scope.model.error.data.StackTrace) {
            //trim whitespace
            $scope.model.error.data.StackTrace = $scope.model.error.data.StackTrace.trim();
        }
        if ($scope.model.error && $scope.model.error.data) {
            $scope.model.error.data.InnerExceptions = [];
            var ex = $scope.model.error.data.InnerException;
            while (ex) {
                if (ex.StackTrace) {
                    ex.StackTrace = ex.StackTrace.trim();
                }
                $scope.model.error.data.InnerExceptions.push(ex);
                ex = ex.InnerException;
            }
        }
    });
    (function () {
        'use strict';
        function NodeNameController($scope) {
            var vm = this;
            var element = angular.element($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'home') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateContent.NodeNameController', NodeNameController);
    }());
    (function () {
        'use strict';
        function DocTypeNameController($scope) {
            var vm = this;
            var element = angular.element($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'home page') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.DocTypeNameController', DocTypeNameController);
    }());
    (function () {
        'use strict';
        function PropertyNameController($scope) {
            var vm = this;
            var element = angular.element($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'welcome text') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.PropertyNameController', PropertyNameController);
    }());
    (function () {
        'use strict';
        function TabNameController($scope) {
            var vm = this;
            var element = angular.element($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'home') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroCreateDocType.TabNameController', TabNameController);
    }());
    (function () {
        'use strict';
        function FolderNameController($scope) {
            var vm = this;
            var element = angular.element($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                if (element.val().toLowerCase() === 'my images') {
                    $scope.model.nextStep();
                } else {
                    vm.error = true;
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.FolderNameController', FolderNameController);
    }());
    (function () {
        'use strict';
        function UploadImagesController($scope, editorState, mediaResource) {
            var vm = this;
            var element = angular.element($scope.model.currentStep.element);
            vm.error = false;
            vm.initNextStep = initNextStep;
            function initNextStep() {
                vm.error = false;
                vm.buttonState = 'busy';
                var currentNode = editorState.getCurrent();
                // make sure we have uploaded at least one image
                mediaResource.getChildren(currentNode.id).then(function (data) {
                    var children = data;
                    if (children.items && children.items.length > 0) {
                        $scope.model.nextStep();
                    } else {
                        vm.error = true;
                    }
                    vm.buttonState = 'init';
                });
            }
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroMediaSection.UploadImagesController', UploadImagesController);
    }());
    (function () {
        'use strict';
        function TemplatesTreeController($scope) {
            var vm = this;
            var eventElement = angular.element($scope.model.currentStep.eventElement);
            function onInit() {
                // check if tree is already open - if it is - go to next step
                if (eventElement.hasClass('icon-navigation-down')) {
                    $scope.model.nextStep();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Tours.UmbIntroRenderInTemplate.TemplatesTreeController', TemplatesTreeController);
    }());
    angular.module('umbraco').controller('Umbraco.Editors.Content.CopyController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
        var dialogOptions = $scope.dialogOptions;
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        $scope.relateToOriginal = true;
        $scope.recursive = true;
        $scope.dialogTreeEventHandler = $({});
        $scope.busy = false;
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        $scope.toggle = toggleHandler;
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        var node = dialogOptions.currentNode;
        function treeLoadedHandler(ev, args) {
            if (node && node.path) {
                $scope.dialogTreeEventHandler.syncTree({
                    path: node.path,
                    activate: false
                });
            }
        }
        function nodeSelectHandler(ev, args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('editors.content.copyController.select', args);
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        function toggleHandler(type) {
            // If the relateToOriginal toggle is clicked
            if (type === 'relate') {
                if ($scope.relateToOriginal) {
                    $scope.relateToOriginal = false;
                    return;
                }
                $scope.relateToOriginal = true;
            }
            // If the recurvise toggle is clicked
            if (type === 'recursive') {
                if ($scope.recursive) {
                    $scope.recursive = false;
                    return;
                }
                $scope.recursive = true;
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            contentResource.copy({
                parentId: $scope.target.id,
                id: node.id,
                relateToOriginal: $scope.relateToOriginal,
                recursive: $scope.recursive
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'content',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'content',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({}, { node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.CreateController
 * @function
 * 
 * @description
 * The controller for the content creation dialog
 */
    function contentCreateController($scope, $routeParams, contentTypeResource, iconHelper, $location, navigationService, blueprintConfig) {
        function initialize() {
            contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
                $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
            });
            $scope.selectContentType = true;
            $scope.selectBlueprint = false;
            $scope.allowBlank = blueprintConfig.allowBlank;
        }
        function close() {
            navigationService.hideMenu();
        }
        function createBlank(docType) {
            $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype=' + docType.alias + '&create=true');
            close();
        }
        function createOrSelectBlueprintIfAny(docType) {
            var blueprintIds = _.keys(docType.blueprints || {});
            $scope.docType = docType;
            if (blueprintIds.length) {
                if (blueprintConfig.skipSelect) {
                    createFromBlueprint(blueprintIds[0]);
                } else {
                    $scope.selectContentType = false;
                    $scope.selectBlueprint = true;
                }
            } else {
                createBlank(docType);
            }
        }
        function createFromBlueprint(blueprintId) {
            $location.path('/content/content/edit/' + $scope.currentNode.id).search('doctype=' + $scope.docType.alias + '&create=true&blueprintId=' + blueprintId);
            close();
        }
        $scope.createBlank = createBlank;
        $scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
        $scope.createFromBlueprint = createFromBlueprint;
        initialize();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.CreateController', contentCreateController);
    angular.module('umbraco').value('blueprintConfig', {
        skipSelect: false,
        allowBlank: true
    });
    (function () {
        function CreateBlueprintController($scope, contentResource, notificationsService, navigationService, localizationService, formHelper, contentEditingHelper) {
            $scope.message = { name: $scope.currentNode.name };
            var successText = {};
            localizationService.localize('blueprints_createBlueprintFrom', ['<em>' + $scope.message.name + '</em>']).then(function (localizedVal) {
                $scope.title = localizedVal;
            });
            $scope.cancel = function () {
                navigationService.hideMenu();
            };
            $scope.create = function () {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: this.blueprintForm,
                        statusMessage: 'Creating blueprint...'
                    })) {
                    contentResource.createBlueprintFromContent($scope.currentNode.id, $scope.message.name).then(function (data) {
                        formHelper.resetForm({
                            scope: $scope,
                            notifications: data.notifications
                        });
                        navigationService.hideMenu();
                    }, function (err) {
                        contentEditingHelper.handleSaveError({
                            redirectOnFailure: false,
                            err: err
                        });
                    });
                }
            };
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.CreateBlueprintController', CreateBlueprintController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function ContentDeleteController($scope, contentResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) {
        $scope.performDelete = function () {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            $scope.busy = true;
            contentResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                treeService.removeNode($scope.currentNode);
                if (rootNode) {
                    //ensure the recycle bin has child nodes now            
                    var recycleBin = treeService.getDescendantNode(rootNode, -20);
                    if (recycleBin) {
                        recycleBin.hasChildren = true;
                        //reload the recycle bin if it's already expanded so the deleted item is shown
                        if (recycleBin.expanded) {
                            treeService.loadNodeChildren({
                                node: recycleBin,
                                section: 'content'
                            });
                        }
                    }
                }
                //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                    //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
                    var location = '/content';
                    if ($scope.currentNode.parentId.toString() === '-20')
                        location = '/content/content/recyclebin';
                    else if ($scope.currentNode.parentId.toString() !== '-1')
                        location = '/content/content/edit/' + $scope.currentNode.parentId;
                    $location.path(location);
                }
                navigationService.hideMenu();
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.busy = false;
                //check if response is ysod
                if (err.status && err.status >= 500) {
                    dialogService.ysodDialog(err);
                }
                if (err.data && angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.DeleteController', ContentDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.EditController
 * @function
 * 
 * @description
 * The controller for the content editor
 */
    function ContentEditController($scope, $routeParams, contentResource) {
        function scaffoldEmpty() {
            return contentResource.getScaffold($routeParams.id, $routeParams.doctype);
        }
        function scaffoldBlueprint() {
            return contentResource.getBlueprintScaffold($routeParams.id, $routeParams.blueprintId);
        }
        $scope.contentId = $routeParams.id;
        $scope.saveMethod = contentResource.save;
        $scope.getMethod = contentResource.getById;
        $scope.getScaffoldMethod = $routeParams.blueprintId ? scaffoldBlueprint : scaffoldEmpty;
        $scope.page = $routeParams.page;
        $scope.isNew = $routeParams.create;
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.EditController', ContentEditController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.EmptyRecycleBinController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function ContentEmptyRecycleBinController($scope, contentResource, treeService, navigationService, notificationsService, $route) {
        $scope.busy = false;
        $scope.performDelete = function () {
            //(used in the UI)
            $scope.busy = true;
            $scope.currentNode.loading = true;
            contentResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
                $scope.busy = false;
                $scope.currentNode.loading = false;
                //show any notifications
                if (angular.isArray(result.notifications)) {
                    for (var i = 0; i < result.notifications.length; i++) {
                        notificationsService.showNotification(result.notifications[i]);
                    }
                }
                treeService.removeChildNodes($scope.currentNode);
                navigationService.hideMenu();
                //reload the current view
                $route.reload();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.EmptyRecycleBinController', ContentEmptyRecycleBinController);
    angular.module('umbraco').controller('Umbraco.Editors.Content.MoveController', function ($scope, userService, eventsService, contentResource, navigationService, appState, treeService, localizationService, notificationsService) {
        var dialogOptions = $scope.dialogOptions;
        var searchText = 'Search...';
        localizationService.localize('general_search').then(function (value) {
            searchText = value + '...';
        });
        $scope.dialogTreeEventHandler = $({});
        $scope.busy = false;
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        var node = dialogOptions.currentNode;
        function treeLoadedHandler(ev, args) {
            if (node && node.path) {
                $scope.dialogTreeEventHandler.syncTree({
                    path: node.path,
                    activate: false
                });
            }
        }
        function nodeSelectHandler(ev, args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('editors.content.moveController.select', args);
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            contentResource.move({
                parentId: $scope.target.id,
                id: node.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currently edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'content',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'content',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({}, { node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    (function () {
        function CreateNotifyController($scope, contentResource, navigationService, angularHelper, localizationService) {
            var vm = this;
            var currentForm;
            vm.notifyOptions = [];
            vm.save = save;
            vm.cancel = cancel;
            vm.message = { name: $scope.currentNode.name };
            vm.labels = {};
            function onInit() {
                vm.loading = true;
                contentResource.getNotifySettingsById($scope.currentNode.id).then(function (options) {
                    currentForm = angularHelper.getCurrentForm($scope);
                    vm.loading = false;
                    vm.notifyOptions = options;
                });
                localizationService.localize('notifications_editNotifications', [$scope.currentNode.name]).then(function (value) {
                    vm.labels.headline = value;
                });
            }
            function cancel() {
                navigationService.hideMenu();
            }
            ;
            function save(notifyOptions) {
                vm.saveState = 'busy';
                vm.saveError = false;
                vm.saveSuccces = false;
                var selectedString = '';
                angular.forEach(notifyOptions, function (option) {
                    if (option.checked === true && option.notifyCode) {
                        selectedString += option.notifyCode;
                    }
                });
                contentResource.setNotifySettingsById($scope.currentNode.id, selectedString).then(function () {
                    vm.saveState = 'success';
                    vm.saveSuccces = true;
                }, function (error) {
                    vm.saveState = 'error';
                    vm.saveError = error;
                });
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.CreateNotifyController', CreateNotifyController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.RecycleBinController
 * @function
 * 
 * @description
 * Controls the recycle bin for content
 * 
 */
    function ContentRecycleBinController($scope, $routeParams, contentResource, navigationService, localizationService) {
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.page = {};
        $scope.page.name = 'Recycle Bin';
        $scope.page.nameLocked = true;
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.listViewPath = null;
        $routeParams.id = '-20';
        contentResource.getRecycleBin().then(function (result) {
            //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a 
            // single property, so we'll extract that property (list view) and use it's data.
            var listproperty = result.tabs[0].properties[0];
            _.each(listproperty.config, function (val, key) {
                $scope.model.config[key] = val;
            });
            $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
        });
        $scope.model = {
            config: {
                entityType: $routeParams.section,
                layouts: []
            }
        };
        // sync tree node
        navigationService.syncTree({
            tree: 'content',
            path: [
                '-1',
                $routeParams.id
            ],
            forceReload: false
        });
        localizePageName();
        function localizePageName() {
            var pageName = 'general_recycleBin';
            localizationService.localize(pageName).then(function (value) {
                $scope.page.name = value;
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.Content.RecycleBinController', ContentRecycleBinController);
    angular.module('umbraco').controller('Umbraco.Editors.Content.RestoreController', function ($scope, relationResource, contentResource, navigationService, appState, treeService, userService, localizationService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.source = _.clone(dialogOptions.currentNode);
        $scope.error = null;
        $scope.loading = true;
        $scope.moving = false;
        $scope.success = false;
        $scope.dialogTreeEventHandler = $({});
        $scope.searchInfo = {
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        $scope.labels = {};
        localizationService.localizeMany(['treeHeaders_content']).then(function (data) {
            $scope.labels.treeRoot = data[0];
        });
        function nodeSelectHandler(ev, args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({}, { node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
        relationResource.getByChildId($scope.source.id, 'relateParentDocumentOnDelete').then(function (data) {
            $scope.loading = false;
            if (!data.length) {
                $scope.moving = true;
                return;
            }
            $scope.relation = data[0];
            if ($scope.relation.parentId == -1) {
                $scope.target = {
                    id: -1,
                    name: $scope.labels.treeRoot
                };
            } else {
                $scope.loading = true;
                contentResource.getById($scope.relation.parentId).then(function (data) {
                    $scope.loading = false;
                    $scope.target = data;
                    // make sure the target item isn't in the recycle bin
                    if ($scope.target.path.indexOf('-20') !== -1) {
                        $scope.moving = true;
                        $scope.target = null;
                    }
                }, function (err) {
                    $scope.loading = false;
                    $scope.error = err;
                });
            }
        }, function (err) {
            $scope.loading = false;
            $scope.error = err;
        });
        $scope.restore = function () {
            $scope.loading = true;
            // this code was copied from `content.move.controller.js`
            contentResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.loading = false;
                $scope.success = true;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'content',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'content',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.loading = false;
                $scope.error = err;
            });
        };
    });
    (function () {
        'use strict';
        function ContentRightsController($scope, $timeout, contentResource, localizationService, angularHelper) {
            var vm = this;
            var currentForm;
            vm.availableUserGroups = [];
            vm.selectedUserGroups = [];
            vm.removedUserGroups = [];
            vm.viewState = 'manageGroups';
            vm.labels = {};
            vm.showNotification = false;
            vm.setViewSate = setViewSate;
            vm.editPermissions = editPermissions;
            vm.setPermissions = setPermissions;
            vm.save = save;
            vm.removePermissions = removePermissions;
            vm.cancelManagePermissions = cancelManagePermissions;
            vm.closeDialog = closeDialog;
            vm.stay = stay;
            function onInit() {
                vm.loading = true;
                contentResource.getDetailedPermissions($scope.currentNode.id).then(function (userGroups) {
                    initData(userGroups);
                    vm.loading = false;
                    currentForm = angularHelper.getCurrentForm($scope);
                });
            }
            /**
        * This will initialize the data and set the correct selectedUserGroups based on the default permissions and explicit permissions assigned
        * @param {any} userGroups
        */
            function initData(userGroups) {
                //reset this
                vm.selectedUserGroups = [];
                vm.availableUserGroups = userGroups;
                angular.forEach(vm.availableUserGroups, function (group) {
                    if (group.permissions) {
                        //if there's explicit permissions assigned than it's selected
                        assignGroupPermissions(group);
                    }
                });
            }
            function setViewSate(state) {
                vm.viewState = state;
            }
            function editPermissions(group) {
                vm.selectedUserGroup = group;
                if (!vm.selectedUserGroup.permissions) {
                    //if no permissions are explicitly set this means we need to show the defaults
                    vm.selectedUserGroup.permissions = vm.selectedUserGroup.defaultPermissions;
                }
                localizationService.localize('defaultdialogs_permissionsSetForGroup', [
                    $scope.currentNode.name,
                    vm.selectedUserGroup.name
                ]).then(function (value) {
                    vm.labels.permissionsSetForGroup = value;
                });
                setViewSate('managePermissions');
            }
            function assignGroupPermissions(group) {
                // clear allowed permissions before we make the list so we don't have duplicates
                group.allowedPermissions = [];
                // get list of checked permissions
                angular.forEach(group.permissions, function (permissionGroup) {
                    angular.forEach(permissionGroup, function (permission) {
                        if (permission.checked) {
                            //the `allowedPermissions` is what will get sent up to the server for saving
                            group.allowedPermissions.push(permission);
                        }
                    });
                });
                if (!group.selected) {
                    // set to selected so we can remove from the dropdown easily
                    group.selected = true;
                    vm.selectedUserGroups.push(group);
                    //remove from the removed groups if it's been re-added
                    vm.removedUserGroups = _.reject(vm.removedUserGroups, function (g) {
                        return g.id == group.id;
                    });
                }
            }
            function setPermissions(group) {
                assignGroupPermissions(group);
                setViewSate('manageGroups');
            }
            /**
         * This essentially resets the permissions for a group for this content item, it will remove it from the selected list
         * @param {any} index
         */
            function removePermissions(index) {
                // remove as selected so we can select it from the dropdown again
                var group = vm.selectedUserGroups[index];
                group.selected = false;
                //reset assigned permissions - so it will default back to default permissions
                group.permissions = [];
                group.allowedPermissions = [];
                vm.selectedUserGroups.splice(index, 1);
                //track it in the removed so this gets pushed to the server
                vm.removedUserGroups.push(group);
            }
            function cancelManagePermissions() {
                setViewSate('manageGroups');
            }
            function formatSaveModel(permissionsSave, groupCollection) {
                angular.forEach(groupCollection, function (g) {
                    permissionsSave[g.id] = [];
                    angular.forEach(g.allowedPermissions, function (p) {
                        permissionsSave[g.id].push(p.permissionCode);
                    });
                });
            }
            function save() {
                vm.saveState = 'busy';
                vm.saveError = false;
                vm.saveSuccces = false;
                //this is a dictionary that we need to populate
                var permissionsSave = {};
                //format the selectedUserGroups, then the removedUserGroups since we want to pass data from both collections up
                formatSaveModel(permissionsSave, vm.selectedUserGroups);
                formatSaveModel(permissionsSave, vm.removedUserGroups);
                var saveModel = {
                    contentId: $scope.currentNode.id,
                    permissions: permissionsSave
                };
                contentResource.savePermissions(saveModel).then(function (userGroups) {
                    //re-assign model from server since it could have changed
                    initData(userGroups);
                    // clear dirty state on the form so we don't see the discard changes notification
                    // we use a timeout here because in some cases the initData reformats the userGroups model and triggers a change after the form state was changed
                    $timeout(function () {
                        if (currentForm) {
                            currentForm.$dirty = false;
                        }
                    });
                    vm.saveState = 'success';
                    vm.saveSuccces = true;
                }, function (error) {
                    vm.saveState = 'error';
                    vm.saveError = error;
                });
            }
            function stay() {
                vm.showNotification = false;
            }
            function closeDialog() {
                // check if form has been changed. If it has show discard changes notification
                if (currentForm && currentForm.$dirty) {
                    vm.showNotification = true;
                } else {
                    $scope.nav.hideDialog();
                }
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Content.RightsController', ContentRightsController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentBlueprint.CreateController
 * @function
 *
 * @description
 * The controller for creating content blueprints
 */
    function ContentBlueprintCreateController($scope, $location, contentTypeResource, navigationService) {
        var vm = this;
        var node = $scope.dialogOptions.currentNode;
        vm.createBlueprint = createBlueprint;
        function onInit() {
            vm.loading = true;
            contentTypeResource.getAll().then(function (documentTypes) {
                vm.documentTypes = documentTypes;
                vm.loading = false;
            });
        }
        function createBlueprint(documentType) {
            $location.path('/settings/contentBlueprints/edit/' + node.id).search('create', 'true').search('doctype', documentType.alias);
            navigationService.hideMenu();
        }
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.CreateController', ContentBlueprintCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentBlueprint.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content blueprints
 */
    function ContentBlueprintDeleteController($scope, contentResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            contentResource.deleteBlueprint($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.DeleteController', ContentBlueprintDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.EditController
 * @function
 * 
 * @description
 * The controller for the content editor
 */
    function ContentBlueprintEditController($scope, $routeParams, contentResource) {
        var excludedProps = [
            '_umb_urls',
            '_umb_releasedate',
            '_umb_expiredate',
            '_umb_template'
        ];
        function getScaffold() {
            return contentResource.getScaffold(-1, $routeParams.doctype).then(function (scaffold) {
                var lastTab = scaffold.tabs[scaffold.tabs.length - 1];
                lastTab.properties = _.filter(lastTab.properties, function (p) {
                    return excludedProps.indexOf(p.alias) === -1;
                });
                scaffold.allowPreview = false;
                scaffold.allowedActions = [
                    'A',
                    'S',
                    'C'
                ];
                return scaffold;
            });
        }
        $scope.contentId = $routeParams.id;
        $scope.isNew = $routeParams.id === '-1';
        $scope.saveMethod = contentResource.saveBlueprint;
        $scope.getMethod = contentResource.getBlueprintById;
        $scope.getScaffoldMethod = getScaffold;
    }
    angular.module('umbraco').controller('Umbraco.Editors.ContentBlueprint.EditController', ContentBlueprintEditController);
    function startUpVideosDashboardController($scope, xmlhelper, $log, $http) {
        $scope.videos = [];
        $scope.init = function (url) {
            var proxyUrl = 'dashboard/feedproxy.aspx?url=' + url;
            $http.get(proxyUrl).then(function (data) {
                var feed = $(data.data);
                $('item', feed).each(function (i, item) {
                    var video = {};
                    video.thumbnail = $(item).find('thumbnail').attr('url');
                    video.title = $('title', item).text();
                    video.link = $('guid', item).text();
                    $scope.videos.push(video);
                });
            });
        };
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.StartupVideosController', startUpVideosDashboardController);
    function startUpDynamicContentController($timeout, $scope, dashboardResource, assetsService, tourService, eventsService) {
        var vm = this;
        var evts = [];
        vm.loading = true;
        vm.showDefault = false;
        vm.startTour = startTour;
        function onInit() {
            // load tours
            tourService.getGroupedTours().then(function (groupedTours) {
                vm.tours = groupedTours;
            });
        }
        function startTour(tour) {
            tourService.startTour(tour);
        }
        // default dashboard content
        vm.defaultDashboard = {
            infoBoxes: [
                {
                    title: 'Documentation',
                    description: 'Find the answers to your Umbraco questions',
                    url: 'https://our.umbraco.com/documentation/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=documentation/'
                },
                {
                    title: 'Community',
                    description: 'Find the answers or ask your Umbraco questions',
                    url: 'https://our.umbraco.com/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=our_forum'
                },
                {
                    title: 'Umbraco.tv',
                    description: 'Tutorial videos (some are free, some are on subscription)',
                    url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=tutorial_videos'
                },
                {
                    title: 'Training',
                    description: 'Real-life training and official Umbraco certifications',
                    url: 'https://umbraco.com/training/?utm_source=core&utm_medium=dashboard&utm_content=text&utm_campaign=training'
                }
            ],
            articles: [
                {
                    title: 'Umbraco.TV - Learn from the source!',
                    description: 'Umbraco.TV will help you go from zero to Umbraco hero at a pace that suits you. Our easy to follow online training videos will give you the fundamental knowledge to start building awesome Umbraco websites.',
                    img: 'views/dashboard/default/umbracotv.jpg',
                    url: 'https://umbraco.tv/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=tv',
                    altText: 'Umbraco.TV - Hours of Umbraco Video Tutorials',
                    buttonText: 'Visit Umbraco.TV'
                },
                {
                    title: 'Our Umbraco - The Friendliest Community',
                    description: 'Our Umbraco - the official community site is your one stop for everything Umbraco. Whether you need a question answered or looking for cool plugins, the world\'s best and friendliest community is just a click away.',
                    img: 'views/dashboard/default/ourumbraco.jpg',
                    url: 'https://our.umbraco.com/?utm_source=core&utm_medium=dashboard&utm_content=image&utm_campaign=our',
                    altText: 'Our Umbraco',
                    buttonText: 'Visit Our Umbraco'
                }
            ]
        };
        evts.push(eventsService.on('appState.tour.complete', function (name, completedTour) {
            $timeout(function () {
                angular.forEach(vm.tours, function (tourGroup) {
                    angular.forEach(tourGroup, function (tour) {
                        if (tour.alias === completedTour.alias) {
                            tour.completed = true;
                        }
                    });
                });
            });
        }));
        //proxy remote css through the local server
        assetsService.loadCss(dashboardResource.getRemoteDashboardCssUrl('content'), $scope);
        dashboardResource.getRemoteDashboardContent('content').then(function (data) {
            vm.loading = false;
            //test if we have received valid data
            //we capture it like this, so we avoid UI errors - which automatically triggers ui based on http response code
            if (data && data.sections) {
                vm.dashboard = data;
            } else {
                vm.showDefault = true;
            }
        }, function (exception) {
            console.error(exception);
            vm.loading = false;
            vm.showDefault = true;
        });
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.StartUpDynamicContentController', startUpDynamicContentController);
    function FormsController($scope, $route, $cookieStore, packageResource, localizationService) {
        $scope.installForms = function () {
            $scope.state = localizationService.localize('packager_installStateDownloading');
            packageResource.fetch('CD44CF39-3D71-4C19-B6EE-948E1FAF0525').then(function (pack) {
                $scope.state = localizationService.localize('packager_installStateImporting');
                return packageResource.import(pack);
            }, $scope.error).then(function (pack) {
                $scope.state = localizationService.localize('packager_installStateInstalling');
                return packageResource.installFiles(pack);
            }, $scope.error).then(function (pack) {
                $scope.state = localizationService.localize('packager_installStateRestarting');
                return packageResource.installData(pack);
            }, $scope.error).then(function (pack) {
                $scope.state = localizationService.localize('packager_installStateComplete');
                return packageResource.cleanUp(pack);
            }, $scope.error).then($scope.complete, $scope.error);
        };
        $scope.complete = function (result) {
            var url = window.location.href + '?init=true';
            $cookieStore.put('umbPackageInstallId', result.packageGuid);
            window.location.reload(true);
        };
        $scope.error = function (err) {
            $scope.state = undefined;
            $scope.error = err;
            //This will return a rejection meaning that the promise change above will stop
            return $q.reject();
        };
        function Video_player(videoId) {
            // Get dom elements
            this.container = document.getElementById(videoId);
            this.video = this.container.getElementsByTagName('video')[0];
            //Create controls
            this.controls = document.createElement('div');
            this.controls.className = 'video-controls';
            this.seek_bar = document.createElement('input');
            this.seek_bar.className = 'seek-bar';
            this.seek_bar.type = 'range';
            this.seek_bar.setAttribute('value', '0');
            this.loader = document.createElement('div');
            this.loader.className = 'loader';
            this.progress_bar = document.createElement('span');
            this.progress_bar.className = 'progress-bar';
            // Insert controls
            this.controls.appendChild(this.seek_bar);
            this.container.appendChild(this.controls);
            this.controls.appendChild(this.loader);
            this.loader.appendChild(this.progress_bar);
        }
        Video_player.prototype.seeking = function () {
            // get the value of the seekbar (hidden input[type="range"])
            var time = this.video.duration * (this.seek_bar.value / 100);
            // Update video to seekbar value
            this.video.currentTime = time;
        };
        // Stop video when user initiates seeking
        Video_player.prototype.start_seek = function () {
            this.video.pause();
        };
        // Start video when user stops seeking
        Video_player.prototype.stop_seek = function () {
            this.video.play();
        };
        // Update the progressbar (span.loader) according to video.currentTime
        Video_player.prototype.update_progress_bar = function () {
            // Get video progress in %
            var value = 100 / this.video.duration * this.video.currentTime;
            // Update progressbar
            this.progress_bar.style.width = value + '%';
        };
        // Bind progressbar to mouse when seeking
        Video_player.prototype.handle_mouse_move = function (event) {
            // Get position of progressbar relative to browser window
            var pos = this.progress_bar.getBoundingClientRect().left;
            // Make sure event is reckonized cross-browser
            event = event || window.event;
            // Update progressbar
            this.progress_bar.style.width = event.clientX - pos + 'px';
        };
        // Eventlisteners for seeking
        Video_player.prototype.video_event_handler = function (videoPlayer, interval) {
            // Update the progress bar
            var animate_progress_bar = setInterval(function () {
                videoPlayer.update_progress_bar();
            }, interval);
            // Fire when input value changes (user seeking)
            videoPlayer.seek_bar.addEventListener('change', function () {
                videoPlayer.seeking();
            });
            // Fire when user clicks on seekbar
            videoPlayer.seek_bar.addEventListener('mousedown', function (clickEvent) {
                // Pause video playback
                videoPlayer.start_seek();
                // Stop updating progressbar according to video progress
                clearInterval(animate_progress_bar);
                // Update progressbar to where user clicks
                videoPlayer.handle_mouse_move(clickEvent);
                // Bind progressbar to cursor
                window.onmousemove = function (moveEvent) {
                    videoPlayer.handle_mouse_move(moveEvent);
                };
            });
            // Fire when user releases seekbar
            videoPlayer.seek_bar.addEventListener('mouseup', function () {
                // Unbind progressbar from cursor
                window.onmousemove = null;
                // Start video playback
                videoPlayer.stop_seek();
                // Animate the progressbar
                animate_progress_bar = setInterval(function () {
                    videoPlayer.update_progress_bar();
                }, interval);
            });
        };
        var videoPlayer = new Video_player('video_1');
        videoPlayer.video_event_handler(videoPlayer, 17);
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.FormsDashboardController', FormsController);
    function startupLatestEditsController($scope) {
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.StartupLatestEditsController', startupLatestEditsController);
    function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) {
        var currentUser = {};
        userService.getCurrentUser().then(function (user) {
            currentUser = user;
            // check if the user has access to the root which they will require to see this dashboard
            if (currentUser.startMediaIds.indexOf(-1) >= 0) {
                //get the system media listview
                contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) {
                    $scope.fakeProperty = {
                        alias: 'contents',
                        config: dt.config,
                        description: '',
                        editor: dt.editor,
                        hideLabel: true,
                        id: 1,
                        label: 'Contents:',
                        validation: {
                            mandatory: false,
                            pattern: null
                        },
                        value: '',
                        view: dt.view
                    };
                });
            } else if (currentUser.startMediaIds.length > 0) {
                // redirect to start node
                $location.path('/media/media/edit/' + (currentUser.startMediaIds.length === 0 ? -1 : currentUser.startMediaIds[0]));
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.MediaFolderBrowserDashboardController', MediaFolderBrowserDashboardController);
    function ExamineMgmtController($scope, umbRequestHelper, $log, $http, $q, $timeout) {
        $scope.indexerDetails = [];
        $scope.searcherDetails = [];
        $scope.loading = true;
        function checkProcessing(indexer, checkActionName) {
            umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', checkActionName, { indexerName: indexer.name })), 'Failed to check index processing').then(function (data) {
                if (data !== null && data !== 'null') {
                    //copy all resulting properties
                    for (var k in data) {
                        indexer[k] = data[k];
                    }
                    indexer.isProcessing = false;
                } else {
                    $timeout(function () {
                        //don't continue if we've tried 100 times
                        if (indexer.processingAttempts < 100) {
                            checkProcessing(indexer, checkActionName);
                            //add an attempt
                            indexer.processingAttempts++;
                        } else {
                            //we've exceeded 100 attempts, stop processing
                            indexer.isProcessing = false;
                        }
                    }, 1000);
                }
            });
        }
        $scope.search = function (searcher, e) {
            if (e && e.keyCode !== 13) {
                return;
            }
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearchResults', {
                searcherName: searcher.name,
                query: encodeURIComponent(searcher.searchText),
                queryType: searcher.searchType
            })), 'Failed to search').then(function (searchResults) {
                searcher.isSearching = true;
                searcher.searchResults = searchResults;
            });
        };
        $scope.toggle = function (provider, propName) {
            if (provider[propName] !== undefined) {
                provider[propName] = !provider[propName];
            } else {
                provider[propName] = true;
            }
        };
        $scope.rebuildIndex = function (indexer) {
            if (confirm('This will cause the index to be rebuilt. ' + 'Depending on how much content there is in your site this could take a while. ' + 'It is not recommended to rebuild an index during times of high website traffic ' + 'or when editors are editing content.')) {
                indexer.isProcessing = true;
                indexer.processingAttempts = 0;
                umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostRebuildIndex', { indexerName: indexer.name })), 'Failed to rebuild index').then(function () {
                    //rebuilding has started, nothing is returned accept a 200 status code.
                    //lets poll to see if it is done.
                    $timeout(function () {
                        checkProcessing(indexer, 'PostCheckRebuildIndex');
                    }, 1000);
                });
            }
        };
        $scope.optimizeIndex = function (indexer) {
            if (confirm('This will cause the index to be optimized which will improve its performance. ' + 'It is not recommended to optimize an index during times of high website traffic ' + 'or when editors are editing content.')) {
                indexer.isProcessing = true;
                umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'PostOptimizeIndex', { indexerName: indexer.name })), 'Failed to optimize index').then(function () {
                    //optimizing has started, nothing is returned accept a 200 status code.
                    //lets poll to see if it is done.
                    $timeout(function () {
                        checkProcessing(indexer, 'PostCheckOptimizeIndex');
                    }, 1000);
                });
            }
        };
        $scope.closeSearch = function (searcher) {
            searcher.isSearching = true;
        };
        //go get the data
        //combine two promises and execute when they are both done
        $q.all([
            //get the indexer details
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetIndexerDetails')), 'Failed to retrieve indexer details').then(function (data) {
                $scope.indexerDetails = data;
            }),
            //get the searcher details
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('examineMgmtBaseUrl', 'GetSearcherDetails')), 'Failed to retrieve searcher details').then(function (data) {
                $scope.searcherDetails = data;
                for (var s in $scope.searcherDetails) {
                    $scope.searcherDetails[s].searchType = 'text';
                }
            })
        ]).then(function () {
            //all init loading is complete
            $scope.loading = false;
        });
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.ExamineMgmtController', ExamineMgmtController);
    (function () {
        'use strict';
        function HealthCheckController($scope, healthCheckResource) {
            var SUCCESS = 0;
            var WARNING = 1;
            var ERROR = 2;
            var INFO = 3;
            var vm = this;
            vm.viewState = 'list';
            vm.groups = [];
            vm.selectedGroup = {};
            vm.getStatus = getStatus;
            vm.executeAction = executeAction;
            vm.checkAllGroups = checkAllGroups;
            vm.checkAllInGroup = checkAllInGroup;
            vm.openGroup = openGroup;
            vm.setViewState = setViewState;
            // Get a (grouped) list of all health checks
            healthCheckResource.getAllChecks().then(function (response) {
                vm.groups = response;
            });
            function setGroupGlobalResultType(group) {
                var totalSuccess = 0;
                var totalError = 0;
                var totalWarning = 0;
                var totalInfo = 0;
                // count total number of statusses
                angular.forEach(group.checks, function (check) {
                    angular.forEach(check.status, function (status) {
                        switch (status.resultType) {
                        case SUCCESS:
                            totalSuccess = totalSuccess + 1;
                            break;
                        case WARNING:
                            totalWarning = totalWarning + 1;
                            break;
                        case ERROR:
                            totalError = totalError + 1;
                            break;
                        case INFO:
                            totalInfo = totalInfo + 1;
                            break;
                        }
                    });
                });
                group.totalSuccess = totalSuccess;
                group.totalError = totalError;
                group.totalWarning = totalWarning;
                group.totalInfo = totalInfo;
            }
            // Get the status of an individual check
            function getStatus(check) {
                check.loading = true;
                check.status = null;
                healthCheckResource.getStatus(check.id).then(function (response) {
                    check.loading = false;
                    check.status = response;
                });
            }
            function executeAction(check, index, action) {
                check.loading = true;
                healthCheckResource.executeAction(action).then(function (response) {
                    check.status[index] = response;
                    check.loading = false;
                });
            }
            function checkAllGroups(groups) {
                // set number of checks which has been executed
                for (var i = 0; i < groups.length; i++) {
                    var group = groups[i];
                    checkAllInGroup(group, group.checks);
                }
                vm.groups = groups;
            }
            function checkAllInGroup(group, checks) {
                group.checkCounter = 0;
                group.loading = true;
                angular.forEach(checks, function (check) {
                    check.loading = true;
                    healthCheckResource.getStatus(check.id).then(function (response) {
                        check.status = response;
                        group.checkCounter = group.checkCounter + 1;
                        check.loading = false;
                        // when all checks are done, set global group result
                        if (group.checkCounter === checks.length) {
                            setGroupGlobalResultType(group);
                            group.loading = false;
                        }
                    });
                });
            }
            function openGroup(group) {
                vm.selectedGroup = group;
                vm.viewState = 'details';
            }
            function setViewState(state) {
                vm.viewState = state;
                if (state === 'list') {
                    for (var i = 0; i < vm.groups.length; i++) {
                        var group = vm.groups[i];
                        setGroupGlobalResultType(group);
                    }
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Dashboard.HealthCheckController', HealthCheckController);
    }());
    (function () {
        'use strict';
        function RedirectUrlsController($scope, redirectUrlsResource, notificationsService, localizationService, $q) {
            //...todo
            //search by url or url part
            //search by domain
            //display domain in dashboard results?
            //used to cancel any request in progress if another one needs to take it's place
            var vm = this;
            var canceler = null;
            vm.dashboard = {
                searchTerm: '',
                loading: false,
                urlTrackerDisabled: false,
                userIsAdmin: false
            };
            vm.pagination = {
                pageIndex: 0,
                pageNumber: 1,
                totalPages: 1,
                pageSize: 20
            };
            vm.goToPage = goToPage;
            vm.search = search;
            vm.removeRedirect = removeRedirect;
            vm.disableUrlTracker = disableUrlTracker;
            vm.enableUrlTracker = enableUrlTracker;
            vm.filter = filter;
            vm.checkEnabled = checkEnabled;
            function activate() {
                vm.checkEnabled().then(function () {
                    vm.search();
                });
            }
            function checkEnabled() {
                vm.dashboard.loading = true;
                return redirectUrlsResource.getEnableState().then(function (response) {
                    vm.dashboard.urlTrackerDisabled = response.enabled !== true;
                    vm.dashboard.userIsAdmin = response.userIsAdmin;
                    vm.dashboard.loading = false;
                });
            }
            function goToPage(pageNumber) {
                vm.pagination.pageIndex = pageNumber - 1;
                vm.pagination.pageNumber = pageNumber;
                vm.search();
            }
            function search() {
                vm.dashboard.loading = true;
                var searchTerm = vm.dashboard.searchTerm;
                if (searchTerm === undefined) {
                    searchTerm = '';
                }
                redirectUrlsResource.searchRedirectUrls(searchTerm, vm.pagination.pageIndex, vm.pagination.pageSize).then(function (response) {
                    vm.redirectUrls = response.searchResults;
                    // update pagination
                    vm.pagination.pageIndex = response.currentPage;
                    vm.pagination.pageNumber = response.currentPage + 1;
                    vm.pagination.totalPages = response.pageCount;
                    vm.dashboard.loading = false;
                });
            }
            function removeRedirect(redirectToDelete) {
                localizationService.localize('redirectUrls_confirmRemove', [
                    redirectToDelete.originalUrl,
                    redirectToDelete.destinationUrl
                ]).then(function (value) {
                    var toggleConfirm = confirm(value);
                    if (toggleConfirm) {
                        redirectUrlsResource.deleteRedirectUrl(redirectToDelete.redirectId).then(function () {
                            var index = vm.redirectUrls.indexOf(redirectToDelete);
                            vm.redirectUrls.splice(index, 1);
                            notificationsService.success(localizationService.localize('redirectUrls_redirectRemoved'));
                            // check if new redirects needs to be loaded
                            if (vm.redirectUrls.length === 0 && vm.pagination.totalPages > 1) {
                                // if we are not on the first page - get records from the previous
                                if (vm.pagination.pageIndex > 0) {
                                    vm.pagination.pageIndex = vm.pagination.pageIndex - 1;
                                    vm.pagination.pageNumber = vm.pagination.pageNumber - 1;
                                }
                                search();
                            }
                        }, function (error) {
                            notificationsService.error(localizationService.localize('redirectUrls_redirectRemoveError'));
                        });
                    }
                });
            }
            function disableUrlTracker() {
                localizationService.localize('redirectUrls_confirmDisable').then(function (value) {
                    var toggleConfirm = confirm(value);
                    if (toggleConfirm) {
                        redirectUrlsResource.toggleUrlTracker(true).then(function () {
                            activate();
                            notificationsService.success(localizationService.localize('redirectUrls_disabledConfirm'));
                        }, function (error) {
                            notificationsService.warning(localizationService.localize('redirectUrls_disableError'));
                        });
                    }
                });
            }
            function enableUrlTracker() {
                redirectUrlsResource.toggleUrlTracker(false).then(function () {
                    activate();
                    notificationsService.success(localizationService.localize('redirectUrls_enabledConfirm'));
                }, function (error) {
                    notificationsService.warning(localizationService.localize('redirectUrls_enableError'));
                });
            }
            var filterDebounced = _.debounce(function (e) {
                $scope.$apply(function () {
                    //a canceler exists, so perform the cancelation operation and reset
                    if (canceler) {
                        canceler.resolve();
                        canceler = $q.defer();
                    } else {
                        canceler = $q.defer();
                    }
                    vm.search();
                });
            }, 200);
            function filter() {
                vm.dashboard.loading = true;
                filterDebounced();
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.Dashboard.RedirectUrlsController', RedirectUrlsController);
    }());
    function XmlDataIntegrityReportController($scope, umbRequestHelper, $log, $http) {
        function check(item) {
            var action = item.check;
            umbRequestHelper.resourcePromise($http.get(umbRequestHelper.getApiUrl('xmlDataIntegrityBaseUrl', action)), 'Failed to retrieve data integrity status').then(function (result) {
                item.checking = false;
                item.invalid = result === 'false';
            });
        }
        $scope.fix = function (item) {
            var action = item.fix;
            if (item.fix) {
                if (confirm('This will cause all xml structures for this type to be rebuilt. ' + 'Depending on how much content there is in your site this could take a while. ' + 'It is not recommended to rebuild xml structures if they are not out of sync, during times of high website traffic ' + 'or when editors are editing content.')) {
                    item.fixing = true;
                    umbRequestHelper.resourcePromise($http.post(umbRequestHelper.getApiUrl('xmlDataIntegrityBaseUrl', action)), 'Failed to retrieve data integrity status').then(function (result) {
                        item.fixing = false;
                        item.invalid = result === 'false';
                    });
                }
            }
        };
        $scope.items = {
            'contentXml': {
                label: 'Content in the cmsContentXml table',
                checking: true,
                fixing: false,
                fix: 'FixContentXmlTable',
                check: 'CheckContentXmlTable'
            },
            'mediaXml': {
                label: 'Media in the cmsContentXml table',
                checking: true,
                fixing: false,
                fix: 'FixMediaXmlTable',
                check: 'CheckMediaXmlTable'
            },
            'memberXml': {
                label: 'Members in the cmsContentXml table',
                checking: true,
                fixing: false,
                fix: 'FixMembersXmlTable',
                check: 'CheckMembersXmlTable'
            }
        };
        for (var i in $scope.items) {
            check($scope.items[i]);
        }
    }
    angular.module('umbraco').controller('Umbraco.Dashboard.XmlDataIntegrityReportController', XmlDataIntegrityReportController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataType.CreateController
 * @function
 *
 * @description
 * The controller for the data type creation dialog
 */
    function DataTypeCreateController($scope, $location, navigationService, dataTypeResource, formHelper, appState) {
        $scope.model = {
            folderName: '',
            creatingFolder: false
        };
        var node = $scope.dialogOptions.currentNode;
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: this.createFolderForm,
                    statusMessage: 'Creating folder...'
                })) {
                dataTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'datatypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({ scope: $scope });
                    var section = appState.getSectionState('currentSection');
                }, function (err) {
                });
            }
            ;
        };
        $scope.createDataType = function () {
            $location.search('create', null);
            $location.path('/developer/datatypes/edit/' + node.id).search('create', 'true');
            navigationService.hideMenu();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.CreateController', DataTypeCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            dataTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.performContainerDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            dataTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.DeleteController', DataTypeDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DataType.EditController
 * @function
 *
 * @description
 * The controller for the content editor
 */
    function DataTypeEditController($scope, $routeParams, $location, appState, navigationService, treeService, dataTypeResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService) {
        //setup scope vars
        $scope.page = {};
        $scope.page.loading = false;
        $scope.page.nameLocked = false;
        $scope.page.menu = {};
        $scope.page.menu.currentSection = appState.getSectionState('currentSection');
        $scope.page.menu.currentNode = null;
        var evts = [];
        //method used to configure the pre-values when we retrieve them from the server
        function createPreValueProps(preVals) {
            $scope.preValues = [];
            for (var i = 0; i < preVals.length; i++) {
                $scope.preValues.push({
                    hideLabel: preVals[i].hideLabel,
                    alias: preVals[i].key,
                    description: preVals[i].description,
                    label: preVals[i].label,
                    view: preVals[i].view,
                    value: preVals[i].value,
                    config: preVals[i].config
                });
            }
        }
        //set up the standard data type props
        $scope.properties = {
            selectedEditor: {
                alias: 'selectedEditor',
                description: 'Select a property editor',
                label: 'Property editor'
            },
            selectedEditorId: {
                alias: 'selectedEditorId',
                label: 'Property editor alias'
            }
        };
        //setup the pre-values as props
        $scope.preValues = [];
        if ($routeParams.create) {
            $scope.page.loading = true;
            $scope.showIdentifier = false;
            //we are creating so get an empty data type item
            dataTypeResource.getScaffold($routeParams.id).then(function (data) {
                $scope.preValuesLoaded = true;
                $scope.content = data;
                setHeaderNameState($scope.content);
                //set a shared state
                editorState.set($scope.content);
                $scope.page.loading = false;
            });
        } else {
            loadDataType();
        }
        function loadDataType() {
            $scope.page.loading = true;
            $scope.showIdentifier = true;
            //we are editing so get the content item from the server
            dataTypeResource.getById($routeParams.id).then(function (data) {
                $scope.preValuesLoaded = true;
                $scope.content = data;
                createPreValueProps($scope.content.preValues);
                setHeaderNameState($scope.content);
                //share state
                editorState.set($scope.content);
                //in one particular special case, after we've created a new item we redirect back to the edit
                // route but there might be server validation errors in the collection which we need to display
                // after the redirect, so we will bind all subscriptions which will show the server validation errors
                // if there are any and then clear them so the collection no longer persists them.
                serverValidationManager.executeAndClearAllSubscriptions();
                navigationService.syncTree({
                    tree: 'datatypes',
                    path: data.path
                }).then(function (syncArgs) {
                    $scope.page.menu.currentNode = syncArgs.node;
                });
                $scope.page.loading = false;
            });
        }
        $scope.$watch('content.selectedEditor', function (newVal, oldVal) {
            //when the value changes, we need to dynamically load in the new editor
            if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
                //we are editing so get the content item from the server
                var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
                dataTypeResource.getPreValues(newVal, currDataTypeId).then(function (data) {
                    $scope.preValuesLoaded = true;
                    $scope.content.preValues = data;
                    createPreValueProps($scope.content.preValues);
                    setHeaderNameState($scope.content);
                    //share state
                    editorState.set($scope.content);
                });
            }
        });
        function setHeaderNameState(content) {
            if (content.isSystem == 1) {
                $scope.page.nameLocked = true;
            }
        }
        $scope.save = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                $scope.page.saveButtonState = 'busy';
                dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create).then(function (data) {
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    contentEditingHelper.handleSuccessfulSave({
                        scope: $scope,
                        savedContent: data,
                        rebindCallback: function () {
                            createPreValueProps(data.preValues);
                        }
                    });
                    setHeaderNameState($scope.content);
                    //share state
                    editorState.set($scope.content);
                    navigationService.syncTree({
                        tree: 'datatypes',
                        path: data.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        $scope.page.menu.currentNode = syncArgs.node;
                    });
                    $scope.page.saveButtonState = 'success';
                    dataTypeHelper.rebindChangedProperties($scope.content, data);
                }, function (err) {
                    //NOTE: in the case of data type values we are setting the orig/new props
                    // to be the same thing since that only really matters for content/media.
                    contentEditingHelper.handleSaveError({
                        redirectOnFailure: false,
                        err: err
                    });
                    $scope.page.saveButtonState = 'error';
                    //share state
                    editorState.set($scope.content);
                });
            }
        };
        evts.push(eventsService.on('app.refreshEditor', function (name, error) {
            loadDataType();
        }));
        //ensure to unregister from all events!
        $scope.$on('$destroy', function () {
            for (var e in evts) {
                eventsService.unsubscribe(evts[e]);
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.DataType.EditController', DataTypeEditController);
    angular.module('umbraco').controller('Umbraco.Editors.DataType.MoveController', function ($scope, dataTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            dataTypeResource.move({
                parentId: $scope.target.id,
                id: dialogOptions.currentNode.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'dataTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'dataTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
                eventsService.emit('app.refreshEditor');
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.CreateController
 * @function
 * 
 * @description
 * The controller for creating dictionary items
 */
    function DictionaryCreateController($scope, $location, dictionaryResource, navigationService, notificationsService, formHelper, appState) {
        var vm = this;
        vm.itemKey = '';
        function createItem() {
            var node = $scope.dialogOptions.currentNode;
            dictionaryResource.create(node.id, vm.itemKey).then(function (data) {
                navigationService.hideMenu();
                // set new item as active in tree
                var currPath = node.path ? node.path : '-1';
                navigationService.syncTree({
                    tree: 'dictionary',
                    path: currPath + ',' + data,
                    forceReload: true,
                    activate: true
                });
                // reset form state
                formHelper.resetForm({ scope: $scope });
                // navigate to edit view
                var currentSection = appState.getSectionState('currentSection');
                $location.path('/' + currentSection + '/dictionary/edit/' + data);
            }, function (err) {
                if (err.data && err.data.message) {
                    notificationsService.error(err.data.message);
                    navigationService.hideMenu();
                }
            });
        }
        vm.createItem = createItem;
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.CreateController', DictionaryCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.DeleteController
 * @function
 * 
 * @description
 * The controller for deleting dictionary items
 */
    function DictionaryDeleteController($scope, $location, dictionaryResource, treeService, navigationService, appState) {
        var vm = this;
        function cancel() {
            navigationService.hideDialog();
        }
        function performDelete() {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            $scope.busy = true;
            dictionaryResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                // get the parent id 
                var parentId = $scope.currentNode.parentId;
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
                var currentSection = appState.getSectionState('currentSection');
                if (parentId !== '-1') {
                    // set the view of the parent item
                    $location.path('/' + currentSection + '/dictionary/edit/' + parentId);
                } else {
                    // we have no parent, so redirect to section
                    $location.path('/' + currentSection + '/');
                }
            });
        }
        vm.cancel = cancel;
        vm.performDelete = performDelete;
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.DeleteController', DictionaryDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.EditController
 * @function
 * 
 * @description
 * The controller for editing dictionary items
 */
    function DictionaryEditController($scope, $routeParams, dictionaryResource, treeService, navigationService, appState, editorState, contentEditingHelper, formHelper, notificationsService, localizationService) {
        var vm = this;
        //setup scope vars
        vm.nameDirty = false;
        vm.page = {};
        vm.page.loading = false;
        vm.page.nameLocked = false;
        vm.page.menu = {};
        vm.page.menu.currentSection = appState.getSectionState('currentSection');
        vm.page.menu.currentNode = null;
        vm.description = '';
        function loadDictionary() {
            vm.page.loading = true;
            //we are editing so get the content item from the server
            dictionaryResource.getById($routeParams.id).then(function (data) {
                bindDictionary(data);
                vm.page.loading = false;
            });
        }
        function createTranslationProperty(translation) {
            return {
                alias: translation.isoCode,
                label: translation.displayName,
                hideLabel: false
            };
        }
        function bindDictionary(data) {
            localizationService.localize('dictionaryItem_description').then(function (value) {
                vm.description = value.replace('%0%', data.name);
            });
            // create data for  umb-property displaying
            for (var i = 0; i < data.translations.length; i++) {
                data.translations[i].property = createTranslationProperty(data.translations[i]);
            }
            contentEditingHelper.handleSuccessfulSave({
                scope: $scope,
                savedContent: data
            });
            // set content
            vm.content = data;
            //share state
            editorState.set(vm.content);
            navigationService.syncTree({
                tree: 'dictionary',
                path: data.path,
                forceReload: true
            }).then(function (syncArgs) {
                vm.page.menu.currentNode = syncArgs.node;
            });
        }
        function onInit() {
            loadDictionary();
        }
        function saveDictionary() {
            if (formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                vm.page.saveButtonState = 'busy';
                dictionaryResource.save(vm.content, vm.nameDirty).then(function (data) {
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    bindDictionary(data);
                    vm.page.saveButtonState = 'success';
                }, function (err) {
                    contentEditingHelper.handleSaveError({
                        redirectOnFailure: false,
                        err: err
                    });
                    notificationsService.error(err.data.message);
                    vm.page.saveButtonState = 'error';
                });
            }
        }
        vm.save = saveDictionary;
        $scope.$watch('vm.content.name', function (newVal, oldVal) {
            //when the value changes, we need to set the name dirty
            if (newVal && newVal !== oldVal && typeof oldVal !== 'undefined') {
                vm.nameDirty = true;
            }
        });
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.EditController', DictionaryEditController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Dictionary.ListController
 * @function
 * 
 * @description
 * The controller for listting dictionary items
 */
    function DictionaryListController($scope, $location, dictionaryResource, localizationService, appState) {
        var vm = this;
        vm.title = 'Dictionary overview';
        vm.loading = false;
        vm.items = [];
        function loadList() {
            vm.loading = true;
            dictionaryResource.getList().then(function (data) {
                vm.items = data;
                vm.loading = false;
            });
        }
        function clickItem(id) {
            var currentSection = appState.getSectionState('currentSection');
            $location.path('/' + currentSection + '/dictionary/edit/' + id);
        }
        vm.clickItem = clickItem;
        function onInit() {
            localizationService.localize('dictionaryItem_overviewTitle').then(function (value) {
                vm.title = value;
            });
            loadList();
        }
        onInit();
    }
    angular.module('umbraco').controller('Umbraco.Editors.Dictionary.ListController', DictionaryListController);
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CopyController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            contentTypeResource.copy({
                parentId: $scope.target.id,
                id: dialogOptions.currentNode.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'documentTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'documentTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.CreateController
 * @function
 *
 * @description
 * The controller for the doc type creation dialog
 */
    function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService, localizationService, iconHelper) {
        $scope.model = {
            allowCreateFolder: $scope.dialogOptions.currentNode.parentId === null || $scope.dialogOptions.currentNode.nodeType === 'container',
            folderName: '',
            creatingFolder: false,
            creatingDoctypeCollection: false
        };
        var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
        $scope.model.disableTemplates = disableTemplates;
        var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.showCreateDocTypeCollection = function () {
            $scope.model.creatingDoctypeCollection = true;
            $scope.model.collectionCreateTemplate = !$scope.model.disableTemplates;
            $scope.model.collectionItemCreateTemplate = !$scope.model.disableTemplates;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: this.createFolderForm,
                    statusMessage: localizeCreateFolder
                })) {
                contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'documenttypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({ scope: $scope });
                    var section = appState.getSectionState('currentSection');
                }, function (err) {
                    $scope.error = err;
                    //show any notifications
                    if (angular.isArray(err.data.notifications)) {
                        for (var i = 0; i < err.data.notifications.length; i++) {
                            notificationsService.showNotification(err.data.notifications[i]);
                        }
                    }
                });
            }
        };
        $scope.createCollection = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: this.createDoctypeCollectionForm,
                    statusMessage: 'Creating Doctype Collection...'
                })) {
                // see if we can find matching icons
                var collectionIcon = 'icon-folders', collectionItemIcon = 'icon-document';
                iconHelper.getIcons().then(function (icons) {
                    for (var i = 0; i < icons.length; i++) {
                        // for matching we'll require a full match for collection, partial match for item
                        if (icons[i].substring(5) == $scope.model.collectionName.toLowerCase()) {
                            collectionIcon = icons[i];
                        } else if (icons[i].substring(5).indexOf($scope.model.collectionItemName.toLowerCase()) > -1) {
                            collectionItemIcon = icons[i];
                        }
                    }
                    contentTypeResource.createCollection(node.id, $scope.model.collectionName, $scope.model.collectionCreateTemplate, $scope.model.collectionItemName, $scope.model.collectionItemCreateTemplate, collectionIcon, collectionItemIcon).then(function (collectionData) {
                        navigationService.hideMenu();
                        $location.search('create', null);
                        $location.search('notemplate', null);
                        formHelper.resetForm({ scope: $scope });
                        var section = appState.getSectionState('currentSection');
                        // redirect to the item id
                        $location.path('/settings/documenttypes/edit/' + collectionData.ItemId);
                    }, function (err) {
                        $scope.error = err;
                        //show any notifications
                        if (angular.isArray(err.data.notifications)) {
                            for (var i = 0; i < err.data.notifications.length; i++) {
                                notificationsService.showNotification(err.data.notifications[i]);
                            }
                        }
                    });
                });
            }
        };
        // Disabling logic for creating document type with template if disableTemplates is set to true
        if (!disableTemplates) {
            $scope.createDocType = function () {
                $location.search('create', null);
                $location.search('notemplate', null);
                $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true');
                navigationService.hideMenu();
            };
        }
        $scope.createComponent = function () {
            $location.search('create', null);
            $location.search('notemplate', null);
            $location.path('/settings/documenttypes/edit/' + node.id).search('create', 'true').search('notemplate', 'true');
            navigationService.hideMenu();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.CreateController', DocumentTypesCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content
 */
    function DocumentTypesDeleteController($scope, dataTypeResource, contentTypeResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            contentTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.performContainerDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            contentTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.DeleteController', DocumentTypesDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.EditController
 * @function
 *
 * @description
 * The controller for the content type editor
 */
    (function () {
        'use strict';
        function DocumentTypesEditController($scope, $routeParams, $injector, contentTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService, angularHelper) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            var evts = [];
            var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
            var buttons = [
                {
                    'name': localizationService.localize('general_design'),
                    'alias': 'design',
                    'icon': 'icon-document-dashed-line',
                    'view': 'views/documenttypes/views/design/design.html',
                    'active': true
                },
                {
                    'name': localizationService.localize('general_listView'),
                    'alias': 'listView',
                    'icon': 'icon-list',
                    'view': 'views/documenttypes/views/listview/listview.html'
                },
                {
                    'name': localizationService.localize('general_rights'),
                    'alias': 'permissions',
                    'icon': 'icon-keychain',
                    'view': 'views/documenttypes/views/permissions/permissions.html'
                },
                {
                    'name': localizationService.localize('treeHeaders_templates'),
                    'alias': 'templates',
                    'icon': 'icon-layout',
                    'view': 'views/documenttypes/views/templates/templates.html'
                }
            ];
            vm.save = save;
            vm.currentNode = null;
            vm.contentType = {};
            vm.page = {};
            vm.page.loading = false;
            vm.page.saveButtonState = 'init';
            vm.page.navigation = [];
            loadButtons();
            vm.page.keyboardShortcutsOverview = [
                {
                    'name': localizationService.localize('main_sections'),
                    'shortcuts': [{
                            'description': localizationService.localize('shortcuts_navigateSections'),
                            'keys': [
                                { 'key': '1' },
                                { 'key': '4' }
                            ],
                            'keyRange': true
                        }]
                },
                {
                    'name': localizationService.localize('general_design'),
                    'shortcuts': [
                        {
                            'description': localizationService.localize('shortcuts_addTab'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 't' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addProperty'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'p' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addEditor'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'e' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_editDataType'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'd' }
                            ]
                        }
                    ]
                },
                {
                    'name': localizationService.localize('general_listView'),
                    'shortcuts': [{
                            'description': localizationService.localize('shortcuts_toggleListView'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'l' }
                            ]
                        }]
                },
                {
                    'name': localizationService.localize('general_rights'),
                    'shortcuts': [
                        {
                            'description': localizationService.localize('shortcuts_toggleAllowAsRoot'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'r' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addChildNode'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'c' }
                            ]
                        }
                    ]
                },
                {
                    'name': localizationService.localize('treeHeaders_templates'),
                    'shortcuts': [{
                            'description': localizationService.localize('shortcuts_addTemplate'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 't' }
                            ]
                        }]
                }
            ];
            contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                vm.page.modelsBuilder = result;
                if (result) {
                    //Models builder mode:
                    vm.page.defaultButton = {
                        alias: 'save',
                        hotKey: 'ctrl+s',
                        hotKeyWhenHidden: true,
                        labelKey: 'buttons_save',
                        letter: 'S',
                        type: 'submit',
                        handler: function () {
                            vm.save();
                        }
                    };
                    vm.page.subButtons = [{
                            alias: 'saveAndGenerateModels',
                            hotKey: 'ctrl+g',
                            hotKeyWhenHidden: true,
                            labelKey: 'buttons_saveAndGenerateModels',
                            letter: 'G',
                            handler: function () {
                                vm.page.saveButtonState = 'busy';
                                vm.save().then(function (result) {
                                    vm.page.saveButtonState = 'busy';
                                    localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                        localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                            notificationsService.info(headerValue, msgValue);
                                        });
                                    });
                                    contentTypeHelper.generateModels().then(function (result) {
                                        // generateModels() returns the dashboard content
                                        if (!result.lastError) {
                                            //re-check model status
                                            contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                vm.page.modelsBuilder = statusResult;
                                            });
                                            //clear and add success
                                            vm.page.saveButtonState = 'init';
                                            localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                notificationsService.success(value);
                                            });
                                        } else {
                                            vm.page.saveButtonState = 'error';
                                            localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                notificationsService.error(value);
                                            });
                                        }
                                    }, function () {
                                        vm.page.saveButtonState = 'error';
                                        localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                            notificationsService.error(value);
                                        });
                                    });
                                });
                            }
                        }];
                }
            });
            if ($routeParams.create) {
                vm.page.loading = true;
                //we are creating so get an empty data type item
                contentTypeResource.getScaffold($routeParams.id).then(function (dt) {
                    init(dt);
                    vm.page.loading = false;
                });
            } else {
                loadDocumentType();
            }
            function loadDocumentType() {
                vm.page.loading = true;
                contentTypeResource.getById($routeParams.id).then(function (dt) {
                    init(dt);
                    syncTreeNode(vm.contentType, dt.path, true);
                    vm.page.loading = false;
                });
            }
            function loadButtons() {
                angular.forEach(buttons, function (val, index) {
                    if (disableTemplates === true && val.alias === 'templates') {
                        buttons.splice(index, 1);
                    }
                });
                vm.page.navigation = buttons;
            }
            /* ---------- SAVE ---------- */
            function save() {
                // only save if there is no overlays open
                if (overlayHelper.getNumberOfOverlays() === 0) {
                    var deferred = $q.defer();
                    vm.page.saveButtonState = 'busy';
                    // reformat allowed content types to array if id's
                    vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
                    contentEditingHelper.contentEditorPerformSave({
                        statusMessage: localizeSaving,
                        saveMethod: contentTypeResource.save,
                        scope: $scope,
                        content: vm.contentType,
                        //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
                        // type when server side validation fails - as opposed to content where we are capable of saving the content
                        // item if server side validation fails
                        redirectOnFailure: false,
                        // we need to rebind... the IDs that have been created!
                        rebindCallback: function (origContentType, savedContentType) {
                            vm.contentType.id = savedContentType.id;
                            vm.contentType.groups.forEach(function (group) {
                                if (!group.name)
                                    return;
                                var k = 0;
                                while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
                                    k++;
                                if (k == savedContentType.groups.length) {
                                    group.id = 0;
                                    return;
                                }
                                var savedGroup = savedContentType.groups[k];
                                if (!group.id)
                                    group.id = savedGroup.id;
                                group.properties.forEach(function (property) {
                                    if (property.id || !property.alias)
                                        return;
                                    k = 0;
                                    while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
                                        k++;
                                    if (k == savedGroup.properties.length) {
                                        property.id = 0;
                                        return;
                                    }
                                    var savedProperty = savedGroup.properties[k];
                                    property.id = savedProperty.id;
                                });
                            });
                        }
                    }).then(function (data) {
                        //success
                        syncTreeNode(vm.contentType, data.path);
                        vm.page.saveButtonState = 'success';
                        deferred.resolve(data);
                    }, function (err) {
                        //error
                        if (err) {
                            editorState.set($scope.content);
                        } else {
                            localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                    notificationsService.error(headerValue, msgValue);
                                });
                            });
                        }
                        vm.page.saveButtonState = 'error';
                        deferred.reject(err);
                    });
                    return deferred.promise;
                }
            }
            function init(contentType) {
                // set all tab to inactive
                if (contentType.groups.length !== 0) {
                    angular.forEach(contentType.groups, function (group) {
                        angular.forEach(group.properties, function (property) {
                            // get data type details for each property
                            getDataTypeDetails(property);
                        });
                    });
                }
                // insert template on new doc types
                if (!$routeParams.notemplate && contentType.id === 0) {
                    contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate);
                    contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates);
                }
                // convert icons for content type
                convertLegacyIcons(contentType);
                //set a shared state
                editorState.set(contentType);
                vm.contentType = contentType;
            }
            function convertLegacyIcons(contentType) {
                // make array to store contentType icon
                var contentTypeArray = [];
                // push icon to array
                contentTypeArray.push({ 'icon': contentType.icon });
                // run through icon method
                iconHelper.formatContentTypeIcons(contentTypeArray);
                // set icon back on contentType
                contentType.icon = contentTypeArray[0].icon;
            }
            function getDataTypeDetails(property) {
                if (property.propertyState !== 'init') {
                    dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                        property.dataTypeIcon = dataType.icon;
                        property.dataTypeName = dataType.name;
                    });
                }
            }
            /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
            function syncTreeNode(dt, path, initialLoad) {
                navigationService.syncTree({
                    tree: 'documenttypes',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    vm.currentNode = syncArgs.node;
                });
            }
            evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                loadDocumentType();
            }));
            //ensure to unregister from all events!
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
            // #3368 - changes on the other "buttons" do not register on the current form, so we manually have to flag the form as dirty 
            $scope.$watch('vm.contentType.allowedContentTypes.length + vm.contentType.allowAsRoot + vm.contentType.allowedTemplates.length + vm.contentType.isContainer', function (newVal, oldVal) {
                if (oldVal === undefined) {
                    // still initializing, ignore
                    return;
                }
                angularHelper.getCurrentForm($scope).$setDirty();
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.EditController', DocumentTypesEditController);
    }());
    angular.module('umbraco').controller('Umbraco.Editors.DocumentTypes.MoveController', function ($scope, contentTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            contentTypeResource.move({
                parentId: $scope.target.id,
                id: dialogOptions.currentNode.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'documentTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'documentTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
                eventsService.emit('app.refreshEditor');
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    angular.module('umbraco').controller('Umbraco.Editors.ContentTypeContainers.RenameController', [
        '$scope',
        '$injector',
        'navigationService',
        'notificationsService',
        'localizationService',
        function (scope, injector, navigationService, notificationsService, localizationService) {
            var notificationHeader;
            function reportSuccessAndClose(treeName) {
                var lastComma = scope.currentNode.path.lastIndexOf(','), path = lastComma === -1 ? scope.currentNode.path : scope.currentNode.path.substring(0, lastComma - 1);
                navigationService.syncTree({
                    tree: treeName,
                    path: path,
                    forceReload: true,
                    activate: true
                });
                localizationService.localize('renamecontainer_folderWasRenamed', [
                    scope.currentNode.name,
                    scope.model.folderName
                ]).then(function (msg) {
                    notificationsService.showNotification({
                        type: 0,
                        header: notificationHeader,
                        message: msg
                    });
                });
                navigationService.hideMenu();
            }
            localizationService.localize('renamecontainer_renamed').then(function (s) {
                notificationHeader = s;
            });
            scope.model = { folderName: scope.currentNode.name };
            scope.renameContainer = function (resourceKey, treeName) {
                var resource = injector.get(resourceKey);
                resource.renameContainer(scope.currentNode.id, scope.model.folderName).then(function () {
                    reportSuccessAndClose(treeName);
                }, function (err) {
                    scope.error = err;
                    if (angular.isArray(err.data.notifications)) {
                        for (var i = 0; i < err.data.notifications.length; i++) {
                            notificationsService.showNotification(err.data.notifications[i]);
                        }
                    }
                });
            };
        }
    ]);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.PropertyController
 * @function
 *
 * @description
 * The controller for the content type editor property dialog
 */
    (function () {
        'use strict';
        function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService) {
            /* ----------- SCOPE VARIABLES ----------- */
            var vm = this;
            var childNodeSelectorOverlayTitle = '';
            vm.contentTypes = [];
            vm.selectedChildren = [];
            vm.overlayTitle = '';
            vm.addChild = addChild;
            vm.removeChild = removeChild;
            vm.toggle = toggle;
            /* ---------- INIT ---------- */
            init();
            function init() {
                childNodeSelectorOverlayTitle = localizationService.localize('contentTypeEditor_chooseChildNode');
                contentTypeResource.getAll().then(function (contentTypes) {
                    vm.contentTypes = contentTypes;
                    // convert legacy icons
                    iconHelper.formatContentTypeIcons(vm.contentTypes);
                    vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.contentTypes);
                    if ($scope.model.id === 0) {
                        contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
                    }
                });
            }
            function addChild($event) {
                vm.childNodeSelectorOverlay = {
                    view: 'itempicker',
                    title: childNodeSelectorOverlayTitle,
                    availableItems: vm.contentTypes,
                    selectedItems: vm.selectedChildren,
                    event: $event,
                    show: true,
                    submit: function (model) {
                        vm.selectedChildren.push(model.selectedItem);
                        $scope.model.allowedContentTypes.push(model.selectedItem.id);
                        vm.childNodeSelectorOverlay.show = false;
                        vm.childNodeSelectorOverlay = null;
                    }
                };
            }
            function removeChild(selectedChild, index) {
                // remove from vm
                vm.selectedChildren.splice(index, 1);
                // remove from content type model
                var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
                $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
            }
            /**
         * Toggle the $scope.model.allowAsRoot value to either true or false
         */
            function toggle() {
                if ($scope.model.allowAsRoot) {
                    $scope.model.allowAsRoot = false;
                    return;
                }
                $scope.model.allowAsRoot = true;
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.DocumentType.PermissionsController', PermissionsController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.TemplatesController
 * @function
 *
 * @description
 * The controller for the content type editor templates sub view
 */
    (function () {
        'use strict';
        function TemplatesController($scope, entityResource, contentTypeHelper, templateResource, $routeParams) {
            /* ----------- SCOPE VARIABLES ----------- */
            var vm = this;
            vm.availableTemplates = [];
            vm.canCreateTemplate = false;
            vm.updateTemplatePlaceholder = false;
            vm.createTemplate = createTemplate;
            /* ---------- INIT ---------- */
            function onInit() {
                entityResource.getAll('Template').then(function (templates) {
                    vm.availableTemplates = templates;
                    // update placeholder template information on new doc types
                    if (!$routeParams.notemplate && $scope.model.id === 0) {
                        vm.updateTemplatePlaceholder = true;
                        vm.availableTemplates = contentTypeHelper.insertTemplatePlaceholder(vm.availableTemplates);
                    }
                    checkIfTemplateExists();
                });
            }
            function createTemplate() {
                vm.createTemplateButtonState = 'busy';
                templateResource.getScaffold(-1).then(function (template) {
                    template.alias = $scope.model.alias;
                    template.name = $scope.model.name;
                    templateResource.save(template).then(function (savedTemplate) {
                        // add icon
                        savedTemplate.icon = 'icon-layout';
                        vm.availableTemplates.push(savedTemplate);
                        vm.canCreateTemplate = false;
                        $scope.model.allowedTemplates.push(savedTemplate);
                        if ($scope.model.defaultTemplate === null) {
                            $scope.model.defaultTemplate = savedTemplate;
                        }
                        vm.createTemplateButtonState = 'success';
                    }, function () {
                        vm.createTemplateButtonState = 'error';
                    });
                }, function () {
                    vm.createTemplateButtonState = 'error';
                });
            }
            ;
            function checkIfTemplateExists() {
                var existingTemplate = vm.availableTemplates.find(function (availableTemplate) {
                    return availableTemplate.name === $scope.model.name || availableTemplate.placeholder;
                });
                vm.canCreateTemplate = existingTemplate ? false : true;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.DocumentType.TemplatesController', TemplatesController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Media.CreateController
 * @function
 * 
 * @description
 * The controller for the media creation dialog
 */
    function mediaCreateController($scope, $routeParams, $location, mediaTypeResource, iconHelper, navigationService) {
        mediaTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
            $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
        });
        $scope.createMediaItem = function (docType) {
            $location.path('/media/media/edit/' + $scope.currentNode.id).search('doctype', docType.alias).search('create', 'true');
            navigationService.hideMenu();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.CreateController', mediaCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ContentDeleteController
 * @function
 * 
 * @description
 * The controller for deleting content
 */
    function MediaDeleteController($scope, mediaResource, treeService, navigationService, editorState, $location, dialogService, notificationsService) {
        $scope.performDelete = function () {
            // stop from firing again on double-click
            if ($scope.busy) {
                return false;
            }
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            $scope.busy = true;
            mediaResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                treeService.removeNode($scope.currentNode);
                if (rootNode) {
                    //ensure the recycle bin has child nodes now            
                    var recycleBin = treeService.getDescendantNode(rootNode, -21);
                    if (recycleBin) {
                        recycleBin.hasChildren = true;
                        //reload the recycle bin if it's already expanded so the deleted item is shown
                        if (recycleBin.expanded) {
                            treeService.loadNodeChildren({
                                node: recycleBin,
                                section: 'media'
                            });
                        }
                    }
                }
                //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                if (editorState.current && editorState.current.id == $scope.currentNode.id) {
                    //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent
                    var location = '/media';
                    if ($scope.currentNode.parentId.toString() === '-21')
                        location = '/media/media/recyclebin';
                    else if ($scope.currentNode.parentId.toString() !== '-1')
                        location = '/media/media/edit/' + $scope.currentNode.parentId;
                    $location.path(location);
                }
                navigationService.hideMenu();
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.busy = false;
                //check if response is ysod
                if (err.status && err.status >= 500) {
                    dialogService.ysodDialog(err);
                }
                if (err.data && angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.DeleteController', MediaDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Media.EditController
 * @function
 * 
 * @description
 * The controller for the media editor
 */
    function mediaEditController($scope, $routeParams, appState, mediaResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
        //setup scope vars
        $scope.currentSection = appState.getSectionState('currentSection');
        $scope.currentNode = null;
        //the editors affiliated node
        $scope.page = {};
        $scope.page.loading = false;
        $scope.page.menu = {};
        $scope.page.menu.currentSection = appState.getSectionState('currentSection');
        $scope.page.menu.currentNode = null;
        //the editors affiliated node
        $scope.page.listViewPath = null;
        $scope.page.saveButtonState = 'init';
        /** Syncs the content item to it's tree node - this occurs on first load and after saving */
        function syncTreeNode(content, path, initialLoad) {
            if (!$scope.content.isChildOfListView) {
                navigationService.syncTree({
                    tree: 'media',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    $scope.page.menu.currentNode = syncArgs.node;
                });
            } else if (initialLoad === true) {
                //it's a child item, just sync the ui node to the parent
                navigationService.syncTree({
                    tree: 'media',
                    path: path.substring(0, path.lastIndexOf(',')).split(','),
                    forceReload: initialLoad !== true
                });
                //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node 
                // from the server so that we can load in the actions menu.
                umbRequestHelper.resourcePromise($http.get(content.treeNodeUrl), 'Failed to retrieve data for child node ' + content.id).then(function (node) {
                    $scope.page.menu.currentNode = node;
                });
            }
        }
        if ($routeParams.create) {
            $scope.page.loading = true;
            mediaResource.getScaffold($routeParams.id, $routeParams.doctype).then(function (data) {
                $scope.content = data;
                editorState.set($scope.content);
                // We don't get the info tab from the server from version 7.8 so we need to manually add it
                contentEditingHelper.addInfoTab($scope.content.tabs);
                $scope.page.loading = false;
            });
        } else {
            $scope.page.loading = true;
            mediaResource.getById($routeParams.id).then(function (data) {
                $scope.content = data;
                if (data.isChildOfListView && data.trashed === false) {
                    $scope.page.listViewPath = $routeParams.page ? '/media/media/edit/' + data.parentId + '?page=' + $routeParams.page : '/media/media/edit/' + data.parentId;
                }
                editorState.set($scope.content);
                //in one particular special case, after we've created a new item we redirect back to the edit
                // route but there might be server validation errors in the collection which we need to display
                // after the redirect, so we will bind all subscriptions which will show the server validation errors
                // if there are any and then clear them so the collection no longer persists them.
                serverValidationManager.executeAndClearAllSubscriptions();
                syncTreeNode($scope.content, data.path, true);
                if ($scope.content.parentId && $scope.content.parentId != -1) {
                    //We fetch all ancestors of the node to generate the footer breadcrump navigation
                    entityResource.getAncestors($routeParams.id, 'media').then(function (anc) {
                        $scope.ancestors = anc;
                    });
                }
                // We don't get the info tab from the server from version 7.8 so we need to manually add it
                contentEditingHelper.addInfoTab($scope.content.tabs);
                $scope.page.loading = false;
            });
        }
        $scope.save = function () {
            if (!$scope.busy && formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                $scope.busy = true;
                $scope.page.saveButtonState = 'busy';
                mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()).then(function (data) {
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    contentEditingHelper.handleSuccessfulSave({
                        scope: $scope,
                        savedContent: data,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                    });
                    editorState.set($scope.content);
                    $scope.busy = false;
                    syncTreeNode($scope.content, data.path);
                    $scope.page.saveButtonState = 'success';
                }, function (err) {
                    contentEditingHelper.handleSaveError({
                        err: err,
                        redirectOnFailure: true,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                    });
                    //show any notifications
                    if (angular.isArray(err.data.notifications)) {
                        for (var i = 0; i < err.data.notifications.length; i++) {
                            notificationsService.showNotification(err.data.notifications[i]);
                        }
                    }
                    editorState.set($scope.content);
                    $scope.busy = false;
                    $scope.page.saveButtonState = 'error';
                });
            } else {
                $scope.busy = false;
            }
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.EditController', mediaEditController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Media.EmptyRecycleBinController
 * @function
 * 
 * @description
 * The controller for deleting media
 */
    function MediaEmptyRecycleBinController($scope, mediaResource, treeService, navigationService, notificationsService, $route) {
        $scope.busy = false;
        $scope.performDelete = function () {
            //(used in the UI)
            $scope.busy = true;
            $scope.currentNode.loading = true;
            mediaResource.emptyRecycleBin($scope.currentNode.id).then(function (result) {
                $scope.busy = false;
                $scope.currentNode.loading = false;
                //show any notifications
                if (angular.isArray(result.notifications)) {
                    for (var i = 0; i < result.notifications.length; i++) {
                        notificationsService.showNotification(result.notifications[i]);
                    }
                }
                treeService.removeChildNodes($scope.currentNode);
                navigationService.hideMenu();
                //reload the current view
                $route.reload();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.EmptyRecycleBinController', MediaEmptyRecycleBinController);
    //used for the media picker dialog
    angular.module('umbraco').controller('Umbraco.Editors.Media.MoveController', function ($scope, userService, eventsService, mediaResource, appState, treeService, navigationService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        var node = dialogOptions.currentNode;
        $scope.busy = false;
        $scope.searchInfo = {
            searchFromId: null,
            searchFromName: null,
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startMediaIds.length > 0 && userData.startMediaIds.indexOf(-1) == -1;
        });
        function treeLoadedHandler(ev, args) {
            if (node && node.path) {
                $scope.dialogTreeEventHandler.syncTree({
                    path: node.path,
                    activate: false
                });
            }
        }
        function nodeSelectHandler(ev, args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            eventsService.emit('editors.media.moveController.select', args);
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.searchFromId = null;
            $scope.searchInfo.searchFromName = null;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.move = function () {
            $scope.busy = true;
            mediaResource.move({
                parentId: $scope.target.id,
                id: node.id
            }).then(function (path) {
                $scope.busy = false;
                $scope.error = false;
                $scope.success = true;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'media',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'media',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
            });
        };
        $scope.dialogTreeEventHandler.bind('treeLoaded', treeLoadedHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeLoaded', treeLoadedHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({}, { node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Content.MediaRecycleBinController
 * @function
 * 
 * @description
 * Controls the recycle bin for media
 * 
 */
    function MediaRecycleBinController($scope, $routeParams, mediaResource, navigationService, localizationService) {
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.page = {};
        $scope.page.name = 'Recycle Bin';
        $scope.page.nameLocked = true;
        //ensures the list view doesn't actually load until we query for the list view config
        // for the section
        $scope.listViewPath = null;
        $routeParams.id = '-21';
        mediaResource.getRecycleBin().then(function (result) {
            //we'll get the 'content item' for the recycle bin, we know that it will contain a single tab and a 
            // single property, so we'll extract that property (list view) and use it's data.
            var listproperty = result.tabs[0].properties[0];
            _.each(listproperty.config, function (val, key) {
                $scope.model.config[key] = val;
            });
            $scope.listViewPath = 'views/propertyeditors/listview/listview.html';
        });
        $scope.model = {
            config: {
                entityType: $routeParams.section,
                layouts: []
            }
        };
        // sync tree node
        navigationService.syncTree({
            tree: 'media',
            path: [
                '-1',
                $routeParams.id
            ],
            forceReload: false
        });
        localizePageName();
        function localizePageName() {
            var pageName = 'general_recycleBin';
            localizationService.localize(pageName).then(function (value) {
                $scope.page.name = value;
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.Editors.Media.RecycleBinController', MediaRecycleBinController);
    angular.module('umbraco').controller('Umbraco.Editors.Media.RestoreController', function ($scope, relationResource, mediaResource, navigationService, appState, treeService, userService, localizationService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.source = _.clone(dialogOptions.currentNode);
        $scope.error = null;
        $scope.loading = true;
        $scope.moving = false;
        $scope.success = false;
        $scope.dialogTreeEventHandler = $({});
        $scope.searchInfo = {
            showSearch: false,
            results: [],
            selectedSearchResults: []
        };
        $scope.treeModel = { hideHeader: false };
        userService.getCurrentUser().then(function (userData) {
            $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
        });
        $scope.labels = {};
        localizationService.localizeMany(['treeHeaders_media']).then(function (data) {
            $scope.labels.treeRoot = data[0];
        });
        function nodeSelectHandler(ev, args) {
            if (args && args.event) {
                args.event.preventDefault();
                args.event.stopPropagation();
            }
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        function nodeExpandedHandler(ev, args) {
            // open mini list view for list views
            if (args.node.metaData.isContainer) {
                openMiniListView(args.node);
            }
        }
        $scope.hideSearch = function () {
            $scope.searchInfo.showSearch = false;
            $scope.searchInfo.results = [];
        };
        // method to select a search result 
        $scope.selectResult = function (evt, result) {
            result.selected = result.selected === true ? false : true;
            nodeSelectHandler(evt, {
                event: evt,
                node: result
            });
        };
        //callback when there are search results 
        $scope.onSearchResults = function (results) {
            $scope.searchInfo.results = results;
            $scope.searchInfo.showSearch = true;
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.dialogTreeEventHandler.bind('treeNodeExpanded', nodeExpandedHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
            $scope.dialogTreeEventHandler.unbind('treeNodeExpanded', nodeExpandedHandler);
        });
        // Mini list view
        $scope.selectListViewNode = function (node) {
            node.selected = node.selected === true ? false : true;
            nodeSelectHandler({}, { node: node });
        };
        $scope.closeMiniListView = function () {
            $scope.miniListView = undefined;
        };
        function openMiniListView(node) {
            $scope.miniListView = node;
        }
        relationResource.getByChildId($scope.source.id, 'relateParentMediaFolderOnDelete').then(function (data) {
            $scope.loading = false;
            if (!data.length) {
                $scope.moving = true;
                return;
            }
            $scope.relation = data[0];
            if ($scope.relation.parentId == -1) {
                $scope.target = {
                    id: -1,
                    name: $scope.labels.treeRoot
                };
            } else {
                $scope.loading = true;
                mediaResource.getById($scope.relation.parentId).then(function (data) {
                    $scope.loading = false;
                    $scope.target = data;
                    // make sure the target item isn't in the recycle bin
                    if ($scope.target.path.indexOf('-21') !== -1) {
                        $scope.moving = true;
                        $scope.target = null;
                    }
                }, function (err) {
                    $scope.loading = false;
                    $scope.error = err;
                });
            }
        }, function (err) {
            $scope.loading = false;
            $scope.error = err;
        });
        $scope.restore = function () {
            $scope.loading = true;
            // this code was copied from `content.move.controller.js`
            mediaResource.move({
                parentId: $scope.target.id,
                id: $scope.source.id
            }).then(function (path) {
                $scope.loading = false;
                $scope.success = true;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved media item - but don't activate the node,
                //then sync to the currenlty edited media item (note: this might not be the media item that was moved!!)
                navigationService.syncTree({
                    tree: 'media',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'media',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.loading = false;
                $scope.error = err;
            });
        };
    });
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CopyController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.copy = function () {
            $scope.busy = true;
            $scope.error = false;
            mediaTypeResource.copy({
                parentId: $scope.target.id,
                id: dialogOptions.currentNode.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the copied content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was copied!!)
                navigationService.syncTree({
                    tree: 'mediaTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'mediaTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MediaType.CreateController
 * @function
 *
 * @description
 * The controller for the media type creation dialog
 */
    function MediaTypesCreateController($scope, $location, navigationService, mediaTypeResource, formHelper, appState, localizationService) {
        $scope.model = {
            folderName: '',
            creatingFolder: false
        };
        var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: this.createFolderForm,
                    statusMessage: localizeCreateFolder
                })) {
                mediaTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'mediatypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({ scope: $scope });
                    var section = appState.getSectionState('currentSection');
                }, function (err) {
                    $scope.error = err;
                });
            }
            ;
        };
        $scope.createMediaType = function () {
            $location.search('create', null);
            $location.path('/settings/mediatypes/edit/' + node.id).search('create', 'true');
            navigationService.hideMenu();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.CreateController', MediaTypesCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MediaType.DeleteController
 * @function
 *
 * @description
 * The controller for the media type delete dialog
 */
    function MediaTypesDeleteController($scope, dataTypeResource, mediaTypeResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            mediaTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.performContainerDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            mediaTypeResource.deleteContainerById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.DeleteController', MediaTypesDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MediaType.EditController
 * @function
 *
 * @description
 * The controller for the media type editor
 */
    (function () {
        'use strict';
        function MediaTypesEditController($scope, $routeParams, mediaTypeResource, dataTypeResource, editorState, contentEditingHelper, formHelper, navigationService, iconHelper, contentTypeHelper, notificationsService, $filter, $q, localizationService, overlayHelper, eventsService) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            var evts = [];
            vm.save = save;
            vm.currentNode = null;
            vm.contentType = {};
            vm.page = {};
            vm.page.loading = false;
            vm.page.saveButtonState = 'init';
            vm.page.navigation = [
                {
                    'name': localizationService.localize('general_design'),
                    'icon': 'icon-document-dashed-line',
                    'view': 'views/mediatypes/views/design/design.html',
                    'active': true
                },
                {
                    'name': localizationService.localize('general_listView'),
                    'icon': 'icon-list',
                    'view': 'views/mediatypes/views/listview/listview.html'
                },
                {
                    'name': localizationService.localize('general_rights'),
                    'icon': 'icon-keychain',
                    'view': 'views/mediatypes/views/permissions/permissions.html'
                }
            ];
            vm.page.keyboardShortcutsOverview = [
                {
                    'name': localizationService.localize('main_sections'),
                    'shortcuts': [{
                            'description': localizationService.localize('shortcuts_navigateSections'),
                            'keys': [
                                { 'key': '1' },
                                { 'key': '3' }
                            ],
                            'keyRange': true
                        }]
                },
                {
                    'name': localizationService.localize('general_design'),
                    'shortcuts': [
                        {
                            'description': localizationService.localize('shortcuts_addTab'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 't' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addProperty'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'p' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addEditor'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'e' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_editDataType'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'd' }
                            ]
                        }
                    ]
                },
                {
                    'name': localizationService.localize('general_listView'),
                    'shortcuts': [{
                            'description': localizationService.localize('shortcuts_toggleListView'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'l' }
                            ]
                        }]
                },
                {
                    'name': localizationService.localize('general_rights'),
                    'shortcuts': [
                        {
                            'description': localizationService.localize('shortcuts_toggleAllowAsRoot'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'r' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addChildNode'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'c' }
                            ]
                        }
                    ]
                }
            ];
            contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                vm.page.modelsBuilder = result;
                if (result) {
                    //Models builder mode:
                    vm.page.defaultButton = {
                        hotKey: 'ctrl+s',
                        hotKeyWhenHidden: true,
                        labelKey: 'buttons_save',
                        letter: 'S',
                        type: 'submit',
                        handler: function () {
                            vm.save();
                        }
                    };
                    vm.page.subButtons = [{
                            hotKey: 'ctrl+g',
                            hotKeyWhenHidden: true,
                            labelKey: 'buttons_saveAndGenerateModels',
                            letter: 'G',
                            handler: function () {
                                vm.page.saveButtonState = 'busy';
                                vm.save().then(function (result) {
                                    vm.page.saveButtonState = 'busy';
                                    localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                        localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                            notificationsService.info(headerValue, msgValue);
                                        });
                                    });
                                    contentTypeHelper.generateModels().then(function (result) {
                                        if (!result.lastError) {
                                            //re-check model status
                                            contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                vm.page.modelsBuilder = statusResult;
                                            });
                                            //clear and add success
                                            vm.page.saveButtonState = 'init';
                                            localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                notificationsService.success(value);
                                            });
                                        } else {
                                            vm.page.saveButtonState = 'error';
                                            localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                notificationsService.error(value);
                                            });
                                        }
                                    }, function () {
                                        vm.page.saveButtonState = 'error';
                                        localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                            notificationsService.error(value);
                                        });
                                    });
                                });
                            }
                        }];
                }
            });
            if ($routeParams.create) {
                vm.page.loading = true;
                //we are creating so get an empty data type item
                mediaTypeResource.getScaffold($routeParams.id).then(function (dt) {
                    init(dt);
                    vm.page.loading = false;
                });
            } else {
                loadMediaType();
            }
            function loadMediaType() {
                vm.page.loading = true;
                mediaTypeResource.getById($routeParams.id).then(function (dt) {
                    init(dt);
                    syncTreeNode(vm.contentType, dt.path, true);
                    vm.page.loading = false;
                });
            }
            /* ---------- SAVE ---------- */
            function save() {
                // only save if there is no overlays open
                if (overlayHelper.getNumberOfOverlays() === 0) {
                    var deferred = $q.defer();
                    vm.page.saveButtonState = 'busy';
                    // reformat allowed content types to array if id's
                    vm.contentType.allowedContentTypes = contentTypeHelper.createIdArray(vm.contentType.allowedContentTypes);
                    contentEditingHelper.contentEditorPerformSave({
                        statusMessage: localizeSaving,
                        saveMethod: mediaTypeResource.save,
                        scope: $scope,
                        content: vm.contentType,
                        //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
                        // type when server side validation fails - as opposed to content where we are capable of saving the content
                        // item if server side validation fails
                        redirectOnFailure: false,
                        // we need to rebind... the IDs that have been created!
                        rebindCallback: function (origContentType, savedContentType) {
                            vm.contentType.id = savedContentType.id;
                            vm.contentType.groups.forEach(function (group) {
                                if (!group.name)
                                    return;
                                var k = 0;
                                while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
                                    k++;
                                if (k == savedContentType.groups.length) {
                                    group.id = 0;
                                    return;
                                }
                                var savedGroup = savedContentType.groups[k];
                                if (!group.id)
                                    group.id = savedGroup.id;
                                group.properties.forEach(function (property) {
                                    if (property.id || !property.alias)
                                        return;
                                    k = 0;
                                    while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
                                        k++;
                                    if (k == savedGroup.properties.length) {
                                        property.id = 0;
                                        return;
                                    }
                                    var savedProperty = savedGroup.properties[k];
                                    property.id = savedProperty.id;
                                });
                            });
                        }
                    }).then(function (data) {
                        //success
                        syncTreeNode(vm.contentType, data.path);
                        vm.page.saveButtonState = 'success';
                        deferred.resolve(data);
                    }, function (err) {
                        //error
                        if (err) {
                            editorState.set($scope.content);
                        } else {
                            localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                    notificationsService.error(headerValue, msgValue);
                                });
                            });
                        }
                        vm.page.saveButtonState = 'error';
                        deferred.reject(err);
                    });
                    return deferred.promise;
                }
            }
            function init(contentType) {
                // set all tab to inactive
                if (contentType.groups.length !== 0) {
                    angular.forEach(contentType.groups, function (group) {
                        angular.forEach(group.properties, function (property) {
                            // get data type details for each property
                            getDataTypeDetails(property);
                        });
                    });
                }
                // convert icons for content type
                convertLegacyIcons(contentType);
                //set a shared state
                editorState.set(contentType);
                vm.contentType = contentType;
            }
            function convertLegacyIcons(contentType) {
                // make array to store contentType icon
                var contentTypeArray = [];
                // push icon to array
                contentTypeArray.push({ 'icon': contentType.icon });
                // run through icon method
                iconHelper.formatContentTypeIcons(contentTypeArray);
                // set icon back on contentType
                contentType.icon = contentTypeArray[0].icon;
            }
            function getDataTypeDetails(property) {
                if (property.propertyState !== 'init') {
                    dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                        property.dataTypeIcon = dataType.icon;
                        property.dataTypeName = dataType.name;
                    });
                }
            }
            /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
            function syncTreeNode(dt, path, initialLoad) {
                navigationService.syncTree({
                    tree: 'mediatypes',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    vm.currentNode = syncArgs.node;
                });
            }
            evts.push(eventsService.on('app.refreshEditor', function (name, error) {
                loadMediaType();
            }));
            //ensure to unregister from all events!
            $scope.$on('$destroy', function () {
                for (var e in evts) {
                    eventsService.unsubscribe(evts[e]);
                }
            });
        }
        angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.EditController', MediaTypesEditController);
    }());
    angular.module('umbraco').controller('Umbraco.Editors.MediaTypes.MoveController', function ($scope, mediaTypeResource, treeService, navigationService, notificationsService, appState, eventsService) {
        var dialogOptions = $scope.dialogOptions;
        $scope.dialogTreeEventHandler = $({});
        function nodeSelectHandler(ev, args) {
            args.event.preventDefault();
            args.event.stopPropagation();
            if ($scope.target) {
                //un-select if there's a current one selected
                $scope.target.selected = false;
            }
            $scope.target = args.node;
            $scope.target.selected = true;
        }
        $scope.move = function () {
            $scope.busy = true;
            $scope.error = false;
            mediaTypeResource.move({
                parentId: $scope.target.id,
                id: dialogOptions.currentNode.id
            }).then(function (path) {
                $scope.error = false;
                $scope.success = true;
                $scope.busy = false;
                //first we need to remove the node that launched the dialog
                treeService.removeNode($scope.currentNode);
                //get the currently edited node (if any)
                var activeNode = appState.getTreeState('selectedNode');
                //we need to do a double sync here: first sync to the moved content - but don't activate the node,
                //then sync to the currenlty edited content (note: this might not be the content that was moved!!)
                navigationService.syncTree({
                    tree: 'mediaTypes',
                    path: path,
                    forceReload: true,
                    activate: false
                }).then(function (args) {
                    if (activeNode) {
                        var activeNodePath = treeService.getPath(activeNode).join();
                        //sync to this node now - depending on what was copied this might already be synced but might not be
                        navigationService.syncTree({
                            tree: 'mediaTypes',
                            path: activeNodePath,
                            forceReload: false,
                            activate: true
                        });
                    }
                });
                eventsService.emit('app.refreshEditor');
            }, function (err) {
                $scope.success = false;
                $scope.error = err;
                $scope.busy = false;
                //show any notifications
                if (angular.isArray(err.data.notifications)) {
                    for (var i = 0; i < err.data.notifications.length; i++) {
                        notificationsService.showNotification(err.data.notifications[i]);
                    }
                }
            });
        };
        $scope.dialogTreeEventHandler.bind('treeNodeSelect', nodeSelectHandler);
        $scope.$on('$destroy', function () {
            $scope.dialogTreeEventHandler.unbind('treeNodeSelect', nodeSelectHandler);
        });
    });
    (function () {
        'use strict';
        function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService) {
            /* ----------- SCOPE VARIABLES ----------- */
            var vm = this;
            var childNodeSelectorOverlayTitle = '';
            vm.mediaTypes = [];
            vm.selectedChildren = [];
            vm.addChild = addChild;
            vm.removeChild = removeChild;
            vm.toggle = toggle;
            /* ---------- INIT ---------- */
            init();
            function init() {
                childNodeSelectorOverlayTitle = localizationService.localize('contentTypeEditor_chooseChildNode');
                mediaTypeResource.getAll().then(function (mediaTypes) {
                    vm.mediaTypes = mediaTypes;
                    // convert legacy icons
                    iconHelper.formatContentTypeIcons(vm.mediaTypes);
                    vm.selectedChildren = contentTypeHelper.makeObjectArrayFromId($scope.model.allowedContentTypes, vm.mediaTypes);
                    if ($scope.model.id === 0) {
                        contentTypeHelper.insertChildNodePlaceholder(vm.mediaTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
                    }
                });
            }
            function addChild($event) {
                vm.childNodeSelectorOverlay = {
                    view: 'itempicker',
                    title: childNodeSelectorOverlayTitle,
                    availableItems: vm.mediaTypes,
                    selectedItems: vm.selectedChildren,
                    event: $event,
                    show: true,
                    submit: function (model) {
                        vm.selectedChildren.push(model.selectedItem);
                        $scope.model.allowedContentTypes.push(model.selectedItem.id);
                        vm.childNodeSelectorOverlay.show = false;
                        vm.childNodeSelectorOverlay = null;
                    }
                };
            }
            function removeChild(selectedChild, index) {
                // remove from vm
                vm.selectedChildren.splice(index, 1);
                // remove from content type model
                var selectedChildIndex = $scope.model.allowedContentTypes.indexOf(selectedChild.id);
                $scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
            }
            /**
         * Toggle the $scope.model.allowAsRoot value to either true or false
         */
            function toggle() {
                if ($scope.model.allowAsRoot) {
                    $scope.model.allowAsRoot = false;
                    return;
                }
                $scope.model.allowAsRoot = true;
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.MediaType.PermissionsController', PermissionsController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.CreateController
 * @function
 * 
 * @description
 * The controller for the member creation dialog
 */
    function memberCreateController($scope, $routeParams, memberTypeResource, iconHelper) {
        memberTypeResource.getTypes($scope.currentNode.id).then(function (data) {
            $scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.CreateController', memberCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content
 */
    function MemberDeleteController($scope, memberResource, treeService, navigationService, editorState, $location, $routeParams) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            memberResource.deleteByKey($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                treeService.removeNode($scope.currentNode);
                //if the current edited item is the same one as we're deleting, we need to navigate elsewhere
                if (editorState.current && editorState.current.key == $scope.currentNode.id) {
                    $location.path('/member/member/list/' + ($routeParams.listName ? $routeParams.listName : 'all-members'));
                }
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.DeleteController', MemberDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.EditController
 * @function
 *
 * @description
 * The controller for the member editor
 */
    function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, umbRequestHelper, $http) {
        //setup scope vars
        $scope.page = {};
        $scope.page.loading = true;
        $scope.page.menu = {};
        $scope.page.menu.currentSection = appState.getSectionState('currentSection');
        $scope.page.menu.currentNode = null;
        //the editors affiliated node
        $scope.page.nameLocked = false;
        $scope.page.listViewPath = null;
        $scope.page.saveButtonState = 'init';
        $scope.page.exportButton = 'init';
        $scope.busy = false;
        $scope.page.listViewPath = $routeParams.page && $routeParams.listName ? '/member/member/list/' + $routeParams.listName + '?page=' + $routeParams.page : null;
        //build a path to sync the tree with
        function buildTreePath(data) {
            return $routeParams.listName ? '-1,' + $routeParams.listName : '-1';
        }
        if ($routeParams.create) {
            //if there is no doc type specified then we are going to assume that
            // we are not using the umbraco membership provider
            if ($routeParams.doctype) {
                //we are creating so get an empty member item
                memberResource.getScaffold($routeParams.doctype).then(function (data) {
                    $scope.content = data;
                    setHeaderNameState($scope.content);
                    editorState.set($scope.content);
                    $scope.page.loading = false;
                });
            } else {
                memberResource.getScaffold().then(function (data) {
                    $scope.content = data;
                    setHeaderNameState($scope.content);
                    editorState.set($scope.content);
                    $scope.page.loading = false;
                });
            }
        } else {
            //so, we usually refernce all editors with the Int ID, but with members we have
            //a different pattern, adding a route-redirect here to handle this:
            //isNumber doesnt work here since its seen as a string
            //TODO: Why is this here - I don't understand why this would ever be an integer? This will not work when we support non-umbraco membership providers.
            if ($routeParams.id && $routeParams.id.length < 9) {
                entityResource.getById($routeParams.id, 'Member').then(function (entity) {
                    $location.path('/member/member/edit/' + entity.key);
                });
            } else {
                //we are editing so get the content item from the server
                memberResource.getByKey($routeParams.id).then(function (data) {
                    $scope.content = data;
                    setHeaderNameState($scope.content);
                    editorState.set($scope.content);
                    var path = buildTreePath(data);
                    //sync the tree (only for ui purposes)
                    navigationService.syncTree({
                        tree: 'member',
                        path: path.split(',')
                    });
                    //it's the initial load of the editor, we need to get the tree node
                    // from the server so that we can load in the actions menu.
                    umbRequestHelper.resourcePromise($http.get(data.treeNodeUrl), 'Failed to retrieve data for child node ' + data.key).then(function (node) {
                        $scope.page.menu.currentNode = node;
                    });
                    //in one particular special case, after we've created a new item we redirect back to the edit
                    // route but there might be server validation errors in the collection which we need to display
                    // after the redirect, so we will bind all subscriptions which will show the server validation errors
                    // if there are any and then clear them so the collection no longer persists them.
                    serverValidationManager.executeAndClearAllSubscriptions();
                    $scope.page.loading = false;
                });
            }
        }
        function setHeaderNameState(content) {
            if (content.membershipScenario === 0) {
                $scope.page.nameLocked = true;
            }
        }
        $scope.save = function () {
            if (!$scope.busy && formHelper.submitForm({
                    scope: $scope,
                    statusMessage: 'Saving...'
                })) {
                $scope.busy = true;
                $scope.page.saveButtonState = 'busy';
                memberResource.save($scope.content, $routeParams.create, fileManager.getFiles()).then(function (data) {
                    formHelper.resetForm({
                        scope: $scope,
                        notifications: data.notifications
                    });
                    contentEditingHelper.handleSuccessfulSave({
                        scope: $scope,
                        savedContent: data,
                        //specify a custom id to redirect to since we want to use the GUID
                        redirectId: data.key,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
                    });
                    editorState.set($scope.content);
                    $scope.busy = false;
                    $scope.page.saveButtonState = 'success';
                    var path = buildTreePath(data);
                    //sync the tree (only for ui purposes)
                    navigationService.syncTree({
                        tree: 'member',
                        path: path.split(','),
                        forceReload: true
                    });
                }, function (err) {
                    contentEditingHelper.handleSaveError({
                        redirectOnFailure: false,
                        err: err,
                        rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
                    });
                    editorState.set($scope.content);
                    $scope.busy = false;
                    $scope.page.saveButtonState = 'error';
                });
            } else {
                $scope.busy = false;
            }
        };
        $scope.export = function () {
            var memberKey = $scope.content.key;
            memberResource.exportMemberData(memberKey);
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.EditController', MemberEditController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Member.ListController
 * @function
 *
 * @description
 * The controller for the member list view
 */
    function MemberListController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper, editorState, localizationService) {
        //setup scope vars
        $scope.currentSection = appState.getSectionState('currentSection');
        $scope.currentNode = null;
        //the editors affiliated node
        $scope.page = {};
        $scope.page.lockedName = true;
        $scope.page.loading = true;
        //we are editing so get the content item from the server
        memberResource.getListNode($routeParams.id).then(function (data) {
            $scope.content = data;
            //translate "All Members"
            if ($scope.content != null && $scope.content.name != null && $scope.content.name.replace(' ', '').toLowerCase() == 'allmembers') {
                localizationService.localize('member_allMembers').then(function (value) {
                    $scope.content.name = value;
                });
            }
            editorState.set($scope.content);
            navigationService.syncTree({
                tree: 'member',
                path: data.path.split(',')
            }).then(function (syncArgs) {
                $scope.currentNode = syncArgs.node;
            });
            //in one particular special case, after we've created a new item we redirect back to the edit
            // route but there might be server validation errors in the collection which we need to display
            // after the redirect, so we will bind all subscriptions which will show the server validation errors
            // if there are any and then clear them so the collection no longer persists them.
            serverValidationManager.executeAndClearAllSubscriptions();
            $scope.page.loading = false;
        });
    }
    angular.module('umbraco').controller('Umbraco.Editors.Member.ListController', MemberListController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberType.CreateController
 * @function
 *
 * @description
 * The controller for the member type creation dialog
 */
    function MemberTypesCreateController($scope, $location, navigationService, memberTypeResource, formHelper, appState, localizationService) {
        $scope.model = {
            folderName: '',
            creatingFolder: false
        };
        var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
        $scope.showCreateFolder = function () {
            $scope.model.creatingFolder = true;
        };
        $scope.createContainer = function () {
            if (formHelper.submitForm({
                    scope: $scope,
                    formCtrl: this.createFolderForm,
                    statusMessage: localizeCreateFolder
                })) {
                memberTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) {
                    navigationService.hideMenu();
                    var currPath = node.path ? node.path : '-1';
                    navigationService.syncTree({
                        tree: 'membertypes',
                        path: currPath + ',' + folderId,
                        forceReload: true,
                        activate: true
                    });
                    formHelper.resetForm({ scope: $scope });
                    var section = appState.getSectionState('currentSection');
                }, function (err) {
                });
            }
            ;
        };
        $scope.createMemberType = function () {
            $location.search('create', null);
            $location.path('/settings/membertypes/edit/' + node.id).search('create', 'true');
            navigationService.hideMenu();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.CreateController', MemberTypesCreateController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberTypes.DeleteController
 * @function
 *
 * @description
 * The controller for deleting member types
 */
    function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            memberTypeResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.DeleteController', MemberTypesDeleteController);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.MemberType.EditController
 * @function
 *
 * @description
 * The controller for the member type editor
 */
    (function () {
        'use strict';
        function MemberTypesEditController($scope, $rootScope, $routeParams, $log, $filter, memberTypeResource, dataTypeResource, editorState, iconHelper, formHelper, navigationService, contentEditingHelper, notificationsService, $q, localizationService, overlayHelper, contentTypeHelper) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            vm.save = save;
            vm.currentNode = null;
            vm.contentType = {};
            vm.page = {};
            vm.page.loading = false;
            vm.page.saveButtonState = 'init';
            vm.page.navigation = [{
                    'name': localizationService.localize('general_design'),
                    'icon': 'icon-document-dashed-line',
                    'view': 'views/membertypes/views/design/design.html',
                    'active': true
                }];
            vm.page.keyboardShortcutsOverview = [{
                    'name': localizationService.localize('shortcuts_shortcut'),
                    'shortcuts': [
                        {
                            'description': localizationService.localize('shortcuts_addTab'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 't' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addProperty'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'p' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_addEditor'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'e' }
                            ]
                        },
                        {
                            'description': localizationService.localize('shortcuts_editDataType'),
                            'keys': [
                                { 'key': 'alt' },
                                { 'key': 'shift' },
                                { 'key': 'd' }
                            ]
                        }
                    ]
                }];
            contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
                vm.page.modelsBuilder = result;
                if (result) {
                    //Models builder mode:
                    vm.page.defaultButton = {
                        hotKey: 'ctrl+s',
                        hotKeyWhenHidden: true,
                        labelKey: 'buttons_save',
                        letter: 'S',
                        type: 'submit',
                        handler: function () {
                            vm.save();
                        }
                    };
                    vm.page.subButtons = [{
                            hotKey: 'ctrl+g',
                            hotKeyWhenHidden: true,
                            labelKey: 'buttons_saveAndGenerateModels',
                            letter: 'G',
                            handler: function () {
                                vm.page.saveButtonState = 'busy';
                                vm.save().then(function (result) {
                                    vm.page.saveButtonState = 'busy';
                                    localizationService.localize('modelsBuilder_buildingModels').then(function (headerValue) {
                                        localizationService.localize('modelsBuilder_waitingMessage').then(function (msgValue) {
                                            notificationsService.info(headerValue, msgValue);
                                        });
                                    });
                                    contentTypeHelper.generateModels().then(function (result) {
                                        if (!result.lastError) {
                                            //re-check model status
                                            contentTypeHelper.checkModelsBuilderStatus().then(function (statusResult) {
                                                vm.page.modelsBuilder = statusResult;
                                            });
                                            //clear and add success
                                            vm.page.saveButtonState = 'init';
                                            localizationService.localize('modelsBuilder_modelsGenerated').then(function (value) {
                                                notificationsService.success(value);
                                            });
                                        } else {
                                            vm.page.saveButtonState = 'error';
                                            localizationService.localize('modelsBuilder_modelsExceptionInUlog').then(function (value) {
                                                notificationsService.error(value);
                                            });
                                        }
                                    }, function () {
                                        vm.page.saveButtonState = 'error';
                                        localizationService.localize('modelsBuilder_modelsGeneratedError').then(function (value) {
                                            notificationsService.error(value);
                                        });
                                    });
                                });
                            }
                        }];
                }
            });
            if ($routeParams.create) {
                vm.page.loading = true;
                //we are creating so get an empty data type item
                memberTypeResource.getScaffold($routeParams.id).then(function (dt) {
                    init(dt);
                    vm.page.loading = false;
                });
            } else {
                vm.page.loading = true;
                memberTypeResource.getById($routeParams.id).then(function (dt) {
                    init(dt);
                    syncTreeNode(vm.contentType, dt.path, true);
                    vm.page.loading = false;
                });
            }
            function save() {
                // only save if there is no overlays open
                if (overlayHelper.getNumberOfOverlays() === 0) {
                    var deferred = $q.defer();
                    vm.page.saveButtonState = 'busy';
                    contentEditingHelper.contentEditorPerformSave({
                        statusMessage: localizeSaving,
                        saveMethod: memberTypeResource.save,
                        scope: $scope,
                        content: vm.contentType,
                        //We do not redirect on failure for doc types - this is because it is not possible to actually save the doc
                        // type when server side validation fails - as opposed to content where we are capable of saving the content
                        // item if server side validation fails
                        redirectOnFailure: false,
                        // we need to rebind... the IDs that have been created!
                        rebindCallback: function (origContentType, savedContentType) {
                            vm.contentType.id = savedContentType.id;
                            vm.contentType.groups.forEach(function (group) {
                                if (!group.name)
                                    return;
                                var k = 0;
                                while (k < savedContentType.groups.length && savedContentType.groups[k].name != group.name)
                                    k++;
                                if (k == savedContentType.groups.length) {
                                    group.id = 0;
                                    return;
                                }
                                var savedGroup = savedContentType.groups[k];
                                if (!group.id)
                                    group.id = savedGroup.id;
                                group.properties.forEach(function (property) {
                                    if (property.id || !property.alias)
                                        return;
                                    k = 0;
                                    while (k < savedGroup.properties.length && savedGroup.properties[k].alias != property.alias)
                                        k++;
                                    if (k == savedGroup.properties.length) {
                                        property.id = 0;
                                        return;
                                    }
                                    var savedProperty = savedGroup.properties[k];
                                    property.id = savedProperty.id;
                                });
                            });
                        }
                    }).then(function (data) {
                        //success
                        syncTreeNode(vm.contentType, data.path);
                        vm.page.saveButtonState = 'success';
                        deferred.resolve(data);
                    }, function (err) {
                        //error
                        if (err) {
                            editorState.set($scope.content);
                        } else {
                            localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                                localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                                    notificationsService.error(headerValue, msgValue);
                                });
                            });
                        }
                        vm.page.saveButtonState = 'error';
                        deferred.reject(err);
                    });
                    return deferred.promise;
                }
            }
            function init(contentType) {
                // set all tab to inactive
                if (contentType.groups.length !== 0) {
                    angular.forEach(contentType.groups, function (group) {
                        angular.forEach(group.properties, function (property) {
                            // get data type details for each property
                            getDataTypeDetails(property);
                        });
                    });
                }
                // convert legacy icons
                convertLegacyIcons(contentType);
                //set a shared state
                editorState.set(contentType);
                vm.contentType = contentType;
            }
            function convertLegacyIcons(contentType) {
                // make array to store contentType icon
                var contentTypeArray = [];
                // push icon to array
                contentTypeArray.push({ 'icon': contentType.icon });
                // run through icon method
                iconHelper.formatContentTypeIcons(contentTypeArray);
                // set icon back on contentType
                contentType.icon = contentTypeArray[0].icon;
            }
            function getDataTypeDetails(property) {
                if (property.propertyState !== 'init') {
                    dataTypeResource.getById(property.dataTypeId).then(function (dataType) {
                        property.dataTypeIcon = dataType.icon;
                        property.dataTypeName = dataType.name;
                    });
                }
            }
            /** Syncs the content type  to it's tree node - this occurs on first load and after saving */
            function syncTreeNode(dt, path, initialLoad) {
                navigationService.syncTree({
                    tree: 'membertypes',
                    path: path.split(','),
                    forceReload: initialLoad !== true
                }).then(function (syncArgs) {
                    vm.currentNode = syncArgs.node;
                });
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.EditController', MemberTypesEditController);
    }());
    angular.module('umbraco').controller('Umbraco.Editors.MemberTypes.MoveController', function ($scope) {
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Packages.DeleteController
 * @function
 *
 * @description
 * The controller for deleting content
 */
    function PackageDeleteController($scope, packageResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            packageResource.deleteCreatedPackage($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Packages.DeleteController', PackageDeleteController);
    (function () {
        'use strict';
        function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, localStorageService) {
            //Hack!
            // if there is a cookie value for packageInstallUri then we need to redirect there,
            // the issue is that we still have webforms and we cannot go to a hash location and then window.reload
            // because it will double load it.
            // we will refresh and then navigate there.
            var installPackageUri = localStorageService.get('packageInstallUri');
            if (installPackageUri) {
                localStorageService.remove('packageInstallUri');
            }
            if (installPackageUri && installPackageUri !== 'installed') {
                //navigate to the custom installer screen, if it is just "installed", then we'll 
                //show the installed view
                $location.path(installPackageUri).search('');
            } else {
                var vm = this;
                vm.page = {};
                vm.page.name = 'Packages';
                vm.page.navigation = [
                    {
                        'name': 'Packages',
                        'icon': 'icon-cloud',
                        'view': 'views/packager/views/repo.html',
                        'active': !installPackageUri || installPackageUri === 'navigation'
                    },
                    {
                        'name': 'Installed',
                        'icon': 'icon-box',
                        'view': 'views/packager/views/installed.html',
                        'active': installPackageUri === 'installed'
                    },
                    {
                        'name': 'Install local',
                        'icon': 'icon-add',
                        'view': 'views/packager/views/install-local.html',
                        'active': installPackageUri === 'local'
                    }
                ];
                $timeout(function () {
                    navigationService.syncTree({
                        tree: 'packager',
                        path: '-1'
                    });
                });
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.OverviewController', PackagesOverviewController);
    }());
    (function () {
        'use strict';
        function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, localStorageService, $timeout, $window, localizationService, $q) {
            var vm = this;
            vm.state = 'upload';
            vm.localPackage = {};
            vm.installPackage = installPackage;
            vm.installState = {
                status: '',
                progress: 0
            };
            vm.installCompleted = false;
            vm.zipFile = {
                uploadStatus: 'idle',
                uploadProgress: 0,
                serverErrorMessage: null
            };
            $scope.handleFiles = function (files, event) {
                if (files) {
                    for (var i = 0; i < files.length; i++) {
                        upload(files[i]);
                    }
                }
            };
            function upload(file) {
                Upload.upload({
                    url: umbRequestHelper.getApiUrl('packageInstallApiBaseUrl', 'UploadLocalPackage'),
                    fields: {},
                    file: file
                }).progress(function (evt) {
                    // hack: in some browsers the progress event is called after success
                    // this prevents the UI from going back to a uploading state
                    if (vm.zipFile.uploadStatus !== 'done' && vm.zipFile.uploadStatus !== 'error') {
                        // set view state to uploading
                        vm.state = 'uploading';
                        // calculate progress in percentage
                        var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                        // set percentage property on file
                        vm.zipFile.uploadProgress = progressPercentage;
                        // set uploading status on file
                        vm.zipFile.uploadStatus = 'uploading';
                    }
                }).success(function (data, status, headers, config) {
                    if (data.notifications && data.notifications.length > 0) {
                        // set error status on file
                        vm.zipFile.uploadStatus = 'error';
                        // Throw message back to user with the cause of the error
                        vm.zipFile.serverErrorMessage = data.notifications[0].message;
                    } else {
                        // set done status on file
                        vm.zipFile.uploadStatus = 'done';
                        loadPackage();
                        vm.zipFile.uploadProgress = 100;
                        vm.localPackage = data;
                    }
                }).error(function (evt, status, headers, config) {
                    // set status done
                    vm.zipFile.uploadStatus = 'error';
                    // If file not found, server will return a 404 and display this message
                    if (status === 404) {
                        vm.zipFile.serverErrorMessage = 'File not found';
                    } else if (status == 400) {
                        //it's a validation error
                        vm.zipFile.serverErrorMessage = evt.message;
                    } else {
                        //it's an unhandled error
                        //if the service returns a detailed error
                        if (evt.InnerException) {
                            vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                            //Check if its the common "too large file" exception
                            if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                                vm.zipFile.serverErrorMessage = 'File too large to upload';
                            }
                        } else if (evt.Message) {
                            vm.zipFile.serverErrorMessage = evt.Message;
                        }
                    }
                });
            }
            function loadPackage() {
                if (vm.zipFile.uploadStatus === 'done') {
                    vm.state = 'packageDetails';
                }
            }
            function installPackage() {
                vm.installState.status = localizationService.localize('packager_installStateImporting');
                vm.installState.progress = '0';
                packageResource.import(vm.localPackage).then(function (pack) {
                    vm.installState.progress = '25';
                    vm.installState.status = localizationService.localize('packager_installStateInstalling');
                    return packageResource.installFiles(pack);
                }, installError).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateRestarting');
                    vm.installState.progress = '50';
                    var deferred = $q.defer();
                    //check if the app domain is restarted ever 2 seconds
                    var count = 0;
                    function checkRestart() {
                        $timeout(function () {
                            packageResource.checkRestart(pack).then(function (d) {
                                count++;
                                //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
                                if (d.isRestarting && count < 10) {
                                    checkRestart();
                                } else {
                                    //it's restarted!
                                    deferred.resolve(d);
                                }
                            }, installError);
                        }, 2000);
                    }
                    checkRestart();
                    return deferred.promise;
                }, installError).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateRestarting');
                    vm.installState.progress = '75';
                    return packageResource.installData(pack);
                }, installError).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateComplete');
                    vm.installState.progress = '100';
                    return packageResource.cleanUp(pack);
                }, installError).then(function (result) {
                    if (result.postInstallationPath) {
                        //Put the redirect Uri in a cookie so we can use after reloading
                        localStorageService.set('packageInstallUri', result.postInstallationPath);
                    } else {
                        //set to a constant value so it knows to just go to the installed view
                        localStorageService.set('packageInstallUri', 'installed');
                    }
                    vm.installState.status = localizationService.localize('packager_installStateCompleted');
                    vm.installCompleted = true;
                }, installError);
            }
            function installError() {
                //This will return a rejection meaning that the promise change above will stop
                return $q.reject();
            }
            vm.reloadPage = function () {
                //reload on next digest (after cookie)
                $timeout(function () {
                    $window.location.reload(true);
                });
            };
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.InstallLocalController', PackagesInstallLocalController);
    }());
    (function () {
        'use strict';
        function PackagesInstalledController($scope, $route, $location, packageResource, $timeout, $window, localStorageService, localizationService) {
            var vm = this;
            vm.confirmUninstall = confirmUninstall;
            vm.uninstallPackage = uninstallPackage;
            vm.state = 'list';
            vm.installState = { status: '' };
            vm.package = {};
            function init() {
                packageResource.getInstalled().then(function (packs) {
                    vm.installedPackages = packs;
                });
                vm.installState.status = '';
                vm.state = 'list';
            }
            function confirmUninstall(pck) {
                vm.state = 'packageDetails';
                vm.package = pck;
            }
            function uninstallPackage(installedPackage) {
                vm.installState.status = localizationService.localize('packager_installStateUninstalling');
                vm.installState.progress = '0';
                packageResource.uninstall(installedPackage.id).then(function () {
                    if (installedPackage.files.length > 0) {
                        vm.installState.status = localizationService.localize('packager_installStateComplete');
                        vm.installState.progress = '100';
                        //set this flag so that on refresh it shows the installed packages list
                        localStorageService.set('packageInstallUri', 'installed');
                        //reload on next digest (after cookie)
                        $timeout(function () {
                            $window.location.reload(true);
                        });
                    } else {
                        init();
                    }
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.InstalledController', PackagesInstalledController);
    }());
    (function () {
        'use strict';
        function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, localStorageService, localizationService) {
            var vm = this;
            vm.packageViewState = 'packageList';
            vm.categories = [];
            vm.loading = true;
            vm.pagination = {
                pageNumber: 1,
                totalPages: 10,
                pageSize: 24
            };
            vm.searchQuery = '';
            vm.installState = {
                status: '',
                progress: 0,
                type: 'ok'
            };
            vm.selectCategory = selectCategory;
            vm.showPackageDetails = showPackageDetails;
            vm.setPackageViewState = setPackageViewState;
            vm.nextPage = nextPage;
            vm.prevPage = prevPage;
            vm.goToPage = goToPage;
            vm.installPackage = installPackage;
            vm.downloadPackage = downloadPackage;
            vm.openLightbox = openLightbox;
            vm.closeLightbox = closeLightbox;
            vm.search = search;
            vm.installCompleted = false;
            var currSort = 'Latest';
            //used to cancel any request in progress if another one needs to take it's place
            var canceler = null;
            function getActiveCategory() {
                if (vm.searchQuery !== '') {
                    return '';
                }
                for (var i = 0; i < vm.categories.length; i++) {
                    if (vm.categories[i].active === true) {
                        return vm.categories[i].name;
                    }
                }
                return '';
            }
            function init() {
                vm.loading = true;
                $q.all([
                    ourPackageRepositoryResource.getCategories().then(function (cats) {
                        vm.categories = cats;
                    }),
                    ourPackageRepositoryResource.getPopular(8).then(function (pack) {
                        vm.popular = pack.packages;
                    }),
                    ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort).then(function (pack) {
                        vm.packages = pack.packages;
                        vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                    })
                ]).then(function () {
                    vm.loading = false;
                });
            }
            function selectCategory(selectedCategory, categories) {
                var reset = false;
                for (var i = 0; i < categories.length; i++) {
                    var category = categories[i];
                    if (category.name === selectedCategory.name && category.active === true) {
                        //it's already selected, let's unselect to show all again
                        reset = true;
                    }
                    category.active = false;
                }
                vm.loading = true;
                vm.searchQuery = '';
                var searchCategory = selectedCategory.name;
                if (reset === true) {
                    searchCategory = '';
                }
                currSort = 'Latest';
                $q.all([
                    ourPackageRepositoryResource.getPopular(8, searchCategory).then(function (pack) {
                        vm.popular = pack.packages;
                    }),
                    ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, searchCategory, vm.searchQuery).then(function (pack) {
                        vm.packages = pack.packages;
                        vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                        vm.pagination.pageNumber = 1;
                    })
                ]).then(function () {
                    vm.loading = false;
                    selectedCategory.active = reset === false;
                });
            }
            function showPackageDetails(selectedPackage) {
                ourPackageRepositoryResource.getDetails(selectedPackage.id).then(function (pack) {
                    packageResource.validateInstalled(pack.name, pack.latestVersion).then(function () {
                        //ok, can install
                        vm.package = pack;
                        vm.package.isValid = true;
                        vm.packageViewState = 'packageDetails';
                    }, function () {
                        //nope, cannot install
                        vm.package = pack;
                        vm.package.isValid = false;
                        vm.packageViewState = 'packageDetails';
                    });
                });
            }
            function setPackageViewState(state) {
                if (state) {
                    vm.packageViewState = state;
                }
            }
            function nextPage(pageNumber) {
                ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                    vm.packages = pack.packages;
                    vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                });
            }
            function prevPage(pageNumber) {
                ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                    vm.packages = pack.packages;
                    vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                });
            }
            function goToPage(pageNumber) {
                ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, currSort, getActiveCategory(), vm.searchQuery).then(function (pack) {
                    vm.packages = pack.packages;
                    vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                });
            }
            function downloadPackage(selectedPackage) {
                vm.loading = true;
                packageResource.fetch(selectedPackage.id).then(function (pack) {
                    vm.packageViewState = 'packageInstall';
                    vm.loading = false;
                    vm.localPackage = pack;
                    vm.localPackage.allowed = true;
                }, function (evt, status, headers, config) {
                    if (status == 400) {
                        //it's a validation error
                        vm.installState.type = 'error';
                        vm.zipFile.serverErrorMessage = evt.message;
                    }
                });
            }
            function error(e, args) {
                //This will return a rejection meaning that the promise change above will stop
                return $q.reject();
            }
            function installPackage(selectedPackage) {
                vm.installState.status = localizationService.localize('packager_installStateImporting');
                vm.installState.progress = '0';
                packageResource.import(selectedPackage).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateInstalling');
                    vm.installState.progress = '25';
                    return packageResource.installFiles(pack);
                }, error).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateRestarting');
                    vm.installState.progress = '50';
                    var deferred = $q.defer();
                    //check if the app domain is restarted ever 2 seconds
                    var count = 0;
                    function checkRestart() {
                        $timeout(function () {
                            packageResource.checkRestart(pack).then(function (d) {
                                count++;
                                //if there is an id it means it's not restarted yet but we'll limit it to only check 10 times
                                if (d.isRestarting && count < 10) {
                                    checkRestart();
                                } else {
                                    //it's restarted!
                                    deferred.resolve(d);
                                }
                            }, error);
                        }, 2000);
                    }
                    checkRestart();
                    return deferred.promise;
                }, error).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateRestarting');
                    vm.installState.progress = '75';
                    return packageResource.installData(pack);
                }, error).then(function (pack) {
                    vm.installState.status = localizationService.localize('packager_installStateComplete');
                    vm.installState.progress = '100';
                    return packageResource.cleanUp(pack);
                }, error).then(function (result) {
                    if (result.postInstallationPath) {
                        //Put the redirect Uri in a cookie so we can use after reloading
                        localStorageService.set('packageInstallUri', result.postInstallationPath);
                    }
                    vm.installState.status = localizationService.localize('packager_installStateCompleted');
                    vm.installCompleted = true;
                }, error);
            }
            function openLightbox(itemIndex, items) {
                vm.lightbox = {
                    show: true,
                    items: items,
                    activeIndex: itemIndex
                };
            }
            function closeLightbox() {
                vm.lightbox.show = false;
                vm.lightbox = null;
            }
            var searchDebounced = _.debounce(function (e) {
                $scope.$apply(function () {
                    //a canceler exists, so perform the cancelation operation and reset
                    if (canceler) {
                        canceler.resolve();
                        canceler = $q.defer();
                    } else {
                        canceler = $q.defer();
                    }
                    currSort = vm.searchQuery ? 'Default' : 'Latest';
                    ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, currSort, '', vm.searchQuery, canceler).then(function (pack) {
                        vm.packages = pack.packages;
                        vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize);
                        vm.pagination.pageNumber = 1;
                        vm.loading = false;
                        //set back to null so it can be re-created
                        canceler = null;
                    });
                });
            }, 200);
            function search(searchQuery) {
                vm.loading = true;
                searchDebounced();
            }
            vm.reloadPage = function () {
                //reload on next digest (after cookie)
                $timeout(function () {
                    window.location.reload(true);
                });
            };
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Packages.RepoController', PackagesRepoController);
    }());
    (function () {
        'use strict';
        function PartialViewMacrosCreateController($scope, codefileResource, macroResource, $location, navigationService, formHelper, localizationService, appState) {
            var vm = this;
            var node = $scope.dialogOptions.currentNode;
            var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
            vm.snippets = [];
            vm.createFolderError = '';
            vm.folderName = '';
            vm.fileName = '';
            vm.showSnippets = false;
            vm.creatingFolder = false;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            vm.createFile = createFile;
            vm.createFileWithoutMacro = createFileWithoutMacro;
            vm.showCreateFromSnippet = showCreateFromSnippet;
            vm.createFileFromSnippet = createFileFromSnippet;
            function onInit() {
                codefileResource.getSnippets('partialViewMacros').then(function (snippets) {
                    vm.snippets = snippets;
                });
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form,
                        statusMessage: localizeCreateFolder
                    })) {
                    codefileResource.createContainer('partialViewMacros', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'partialViewMacros',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({ scope: $scope });
                        var section = appState.getSectionState('currentSection');
                    }, function (err) {
                        vm.createFolderError = err;
                        //show any notifications
                        formHelper.showNotifications(err.data);
                    });
                }
            }
            function createFile() {
                $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true');
                navigationService.hideMenu();
            }
            function createFileWithoutMacro() {
                $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true').search('nomacro', 'true');
                navigationService.hideMenu();
            }
            function createFileFromSnippet(snippet) {
                $location.path('/developer/partialviewmacros/edit/' + node.id).search('create', 'true').search('snippet', snippet.fileName);
                navigationService.hideMenu();
            }
            function showCreateFromSnippet() {
                vm.showSnippets = true;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.CreateController', PartialViewMacrosCreateController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.PartialViewMacros.DeleteController
 * @function
 *
 * @description
 * The controller for deleting partial view macros
 */
    function PartialViewMacrosDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            codefileResource.deleteByPath('partialViewMacros', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.DeleteController', PartialViewMacrosDeleteController);
    (function () {
        'use strict';
        function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            vm.page = {};
            vm.page.loading = true;
            vm.partialViewMacroFile = {};
            //menu
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            // bind functions to view model
            vm.save = save;
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
            vm.openMacroOverlay = openMacroOverlay;
            vm.openInsertOverlay = openInsertOverlay;
            /* Functions bound to view model */
            function save() {
                vm.page.saveButtonState = 'busy';
                vm.partialViewMacro.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    statusMessage: localizeSaving,
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.partialViewMacro,
                    // We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view
                    // when server side validation fails - as opposed to content where we are capable of saving the content
                    // item if server side validation fails
                    redirectOnFailure: false,
                    rebindCallback: function (orignal, saved) {
                    }
                }).then(function (saved) {
                    // create macro if needed
                    if ($routeParams.create && $routeParams.nomacro !== 'true') {
                        macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) {
                            completeSave(saved);
                        }, function (err) {
                            //show any notifications
                            formHelper.showNotifications(err.data);
                        });
                    } else {
                        completeSave(saved);
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                        localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                            notificationsService.error(headerValue, msgValue);
                        });
                    });
                });
            }
            function completeSave(saved) {
                localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) {
                    localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) {
                        notificationsService.success(headerValue, msgValue);
                    });
                });
                //check if the name changed, if so we need to redirect
                if (vm.partialViewMacro.id !== saved.id) {
                    contentEditingHelper.redirectToRenamedContent(saved.id);
                } else {
                    vm.page.saveButtonState = 'success';
                    vm.partialViewMacro = saved;
                    //sync state
                    editorState.set(vm.partialViewMacro);
                    // normal tree sync
                    navigationService.syncTree({
                        tree: 'partialViewMacros',
                        path: vm.partialViewMacro.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                    // clear $dirty state on form
                    setFormState('pristine');
                }
            }
            function openInsertOverlay() {
                vm.insertOverlay = {
                    view: 'insert',
                    allowedTypes: {
                        macro: true,
                        dictionary: true,
                        umbracoField: true
                    },
                    hideSubmitButton: true,
                    show: true,
                    submit: function (model) {
                        switch (model.insert.type) {
                        case 'macro':
                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                            insert(macroObject.syntax);
                            break;
                        case 'dictionary':
                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                            insert(code);
                            break;
                        case 'umbracoField':
                            insert(model.insert.umbracoField);
                            break;
                        }
                        vm.insertOverlay.show = false;
                        vm.insertOverlay = null;
                    },
                    close: function (oldModel) {
                        // close the dialog
                        vm.insertOverlay.show = false;
                        vm.insertOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openMacroOverlay() {
                vm.macroPickerOverlay = {
                    view: 'macropicker',
                    dialogData: {},
                    show: true,
                    title: 'Insert macro',
                    submit: function (model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                        insert(macroObject.syntax);
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                    },
                    close: function (oldModel) {
                        // close the dialog
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openPageFieldOverlay() {
                vm.pageFieldOverlay = {
                    submitButtonLabel: 'Insert',
                    closeButtonlabel: 'Cancel',
                    view: 'insertfield',
                    show: true,
                    submit: function (model) {
                        insert(model.umbracoField);
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                    },
                    close: function (model) {
                        // close the dialog
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openDictionaryItemOverlay() {
                vm.dictionaryItemOverlay = {
                    view: 'treepicker',
                    section: 'settings',
                    treeAlias: 'dictionary',
                    entityType: 'dictionary',
                    multiPicker: false,
                    show: true,
                    title: 'Insert dictionary item',
                    emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                    select: function (node) {
                        var code = templateHelper.getInsertDictionarySnippet(node.name);
                        insert(code);
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openQueryBuilderOverlay() {
                vm.queryBuilderOverlay = {
                    view: 'querybuilder',
                    show: true,
                    title: 'Query for content',
                    submit: function (model) {
                        var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                        insert(code);
                        vm.queryBuilderOverlay.show = false;
                        vm.queryBuilderOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.queryBuilderOverlay.show = false;
                        vm.queryBuilderOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            /* Local functions */
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if ($routeParams.create) {
                    var snippet = 'Empty';
                    if ($routeParams.snippet) {
                        snippet = $routeParams.snippet;
                    }
                    codefileResource.getScaffold('partialViewMacros', $routeParams.id, snippet).then(function (partialViewMacro) {
                        if ($routeParams.name) {
                            partialViewMacro.name = $routeParams.name;
                        }
                        ready(partialViewMacro, false);
                    });
                } else {
                    codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) {
                        ready(partialViewMacro, true);
                    });
                }
            }
            function ready(partialViewMacro, syncTree) {
                vm.page.loading = false;
                vm.partialViewMacro = partialViewMacro;
                //sync state
                editorState.set(vm.partialViewMacro);
                if (syncTree) {
                    navigationService.syncTree({
                        tree: 'partialViewMacros',
                        path: vm.partialViewMacro.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                // ace configuration
                vm.aceOption = {
                    mode: 'razor',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: { fontSize: '14px' },
                    onLoad: function (_editor) {
                        vm.editor = _editor;
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new template
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                                persistCurrentLocation();
                            });
                        }
                        //change on blur, focus
                        vm.editor.on('blur', persistCurrentLocation);
                        vm.editor.on('focus', persistCurrentLocation);
                        vm.editor.on('change', changeAceEditor);
                    }
                };
            }
            function insert(str) {
                vm.editor.focus();
                vm.editor.moveCursorToPosition(vm.currentPosition);
                vm.editor.insert(str);
                // set form state to $dirty
                setFormState('dirty');
            }
            function persistCurrentLocation() {
                vm.currentPosition = vm.editor.getCursorPosition();
            }
            function changeAceEditor() {
                setFormState('dirty');
            }
            function setFormState(state) {
                // get the current form
                var currentForm = angularHelper.getCurrentForm($scope);
                // set state
                if (state === 'dirty') {
                    currentForm.$setDirty();
                } else if (state === 'pristine') {
                    currentForm.$setPristine();
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViewMacros.EditController', partialViewMacrosEditController);
    }());
    (function () {
        'use strict';
        function PartialViewsCreateController($scope, codefileResource, $location, navigationService, formHelper, localizationService, appState) {
            var vm = this;
            var node = $scope.dialogOptions.currentNode;
            var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
            vm.snippets = [];
            vm.showSnippets = false;
            vm.creatingFolder = false;
            vm.createFolderError = '';
            vm.folderName = '';
            vm.createPartialView = createPartialView;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            vm.showCreateFromSnippet = showCreateFromSnippet;
            function onInit() {
                codefileResource.getSnippets('partialViews').then(function (snippets) {
                    vm.snippets = snippets;
                });
            }
            function createPartialView(selectedSnippet) {
                var snippet = null;
                if (selectedSnippet && selectedSnippet.fileName) {
                    snippet = selectedSnippet.fileName;
                }
                $location.path('/settings/partialviews/edit/' + node.id).search('create', 'true').search('snippet', snippet);
                navigationService.hideMenu();
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form,
                        statusMessage: localizeCreateFolder
                    })) {
                    codefileResource.createContainer('partialViews', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'partialViews',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({ scope: $scope });
                        var section = appState.getSectionState('currentSection');
                    }, function (err) {
                        vm.createFolderError = err;
                        formHelper.showNotifications(err.data);
                    });
                }
            }
            function showCreateFromSnippet() {
                vm.showSnippets = true;
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViews.CreateController', PartialViewsCreateController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.PartialViews.DeleteController
 * @function
 *
 * @description
 * The controller for deleting partial views
 */
    function PartialViewsDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            // Reset the error message
            $scope.error = null;
            codefileResource.deleteByPath('partialViews', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.error = err;
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.PartialViews.DeleteController', PartialViewsDeleteController);
    (function () {
        'use strict';
        function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            vm.page = {};
            vm.page.loading = true;
            vm.partialView = {};
            //menu
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts());
            vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts());
            vm.page.keyboardShortcutsOverview.push(templateHelper.getPartialViewEditorShortcuts());
            // bind functions to view model
            vm.save = save;
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
            vm.openMacroOverlay = openMacroOverlay;
            vm.openInsertOverlay = openInsertOverlay;
            /* Functions bound to view model */
            function save() {
                vm.page.saveButtonState = 'busy';
                vm.partialView.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    statusMessage: localizeSaving,
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.partialView,
                    //We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews
                    // type when server side validation fails - as opposed to content where we are capable of saving the content
                    // item if server side validation fails
                    redirectOnFailure: false,
                    rebindCallback: function (orignal, saved) {
                    }
                }).then(function (saved) {
                    localizationService.localize('speechBubbles_partialViewSavedHeader').then(function (headerValue) {
                        localizationService.localize('speechBubbles_partialViewSavedText').then(function (msgValue) {
                            notificationsService.success(headerValue, msgValue);
                        });
                    });
                    //check if the name changed, if so we need to redirect
                    if (vm.partialView.id !== saved.id) {
                        contentEditingHelper.redirectToRenamedContent(saved.id);
                    } else {
                        vm.page.saveButtonState = 'success';
                        vm.partialView = saved;
                        //sync state
                        editorState.set(vm.partialView);
                        // normal tree sync
                        navigationService.syncTree({
                            tree: 'partialViews',
                            path: vm.partialView.path,
                            forceReload: true
                        }).then(function (syncArgs) {
                            vm.page.menu.currentNode = syncArgs.node;
                        });
                        // clear $dirty state on form
                        setFormState('pristine');
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localize('speechBubbles_validationFailedHeader').then(function (headerValue) {
                        localizationService.localize('speechBubbles_validationFailedMessage').then(function (msgValue) {
                            notificationsService.error(headerValue, msgValue);
                        });
                    });
                });
            }
            function openInsertOverlay() {
                vm.insertOverlay = {
                    view: 'insert',
                    allowedTypes: {
                        macro: true,
                        dictionary: true,
                        umbracoField: true
                    },
                    hideSubmitButton: true,
                    show: true,
                    submit: function (model) {
                        switch (model.insert.type) {
                        case 'macro':
                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                            insert(macroObject.syntax);
                            break;
                        case 'dictionary':
                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                            insert(code);
                            break;
                        case 'umbracoField':
                            insert(model.insert.umbracoField);
                            break;
                        }
                        vm.insertOverlay.show = false;
                        vm.insertOverlay = null;
                    },
                    close: function (oldModel) {
                        // close the dialog
                        vm.insertOverlay.show = false;
                        vm.insertOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openMacroOverlay() {
                vm.macroPickerOverlay = {
                    view: 'macropicker',
                    dialogData: {},
                    show: true,
                    title: 'Insert macro',
                    submit: function (model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                        insert(macroObject.syntax);
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                    },
                    close: function (oldModel) {
                        // close the dialog
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openPageFieldOverlay() {
                vm.pageFieldOverlay = {
                    submitButtonLabel: 'Insert',
                    closeButtonlabel: 'Cancel',
                    view: 'insertfield',
                    show: true,
                    submit: function (model) {
                        insert(model.umbracoField);
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                    },
                    close: function (model) {
                        // close the dialog
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openDictionaryItemOverlay() {
                vm.dictionaryItemOverlay = {
                    view: 'treepicker',
                    section: 'settings',
                    treeAlias: 'dictionary',
                    entityType: 'dictionary',
                    multiPicker: false,
                    show: true,
                    title: 'Insert dictionary item',
                    emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                    select: function (node) {
                        var code = templateHelper.getInsertDictionarySnippet(node.name);
                        insert(code);
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openQueryBuilderOverlay() {
                vm.queryBuilderOverlay = {
                    view: 'querybuilder',
                    show: true,
                    title: 'Query for content',
                    submit: function (model) {
                        var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                        insert(code);
                        vm.queryBuilderOverlay.show = false;
                        vm.queryBuilderOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.queryBuilderOverlay.show = false;
                        vm.queryBuilderOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            /* Local functions */
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if ($routeParams.create) {
                    var snippet = 'Empty';
                    if ($routeParams.snippet) {
                        snippet = $routeParams.snippet;
                    }
                    codefileResource.getScaffold('partialViews', $routeParams.id, snippet).then(function (partialView) {
                        ready(partialView, false);
                    });
                } else {
                    codefileResource.getByPath('partialViews', $routeParams.id).then(function (partialView) {
                        ready(partialView, true);
                    });
                }
            }
            function ready(partialView, syncTree) {
                vm.page.loading = false;
                vm.partialView = partialView;
                //sync state
                editorState.set(vm.partialView);
                if (syncTree) {
                    navigationService.syncTree({
                        tree: 'partialViews',
                        path: vm.partialView.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                // ace configuration
                vm.aceOption = {
                    mode: 'razor',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: { fontSize: '14px' },
                    onLoad: function (_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        //Unassigns the keybinding (That was previously auto-complete)
                        //As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        // Assign new keybinding
                        _editor.commands.addCommands([
                            //Disable (alt+shift+K)
                            //Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function () {
                                    //Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoValue',
                                bindKey: 'Alt-Shift-V',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openPageFieldOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertDictionary',
                                bindKey: 'Alt-Shift-D',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openDictionaryItemOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoMacro',
                                bindKey: 'Alt-Shift-M',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openMacroOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertQuery',
                                bindKey: 'Alt-Shift-Q',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openQueryBuilderOverlay();
                                    });
                                },
                                readOnly: true
                            }
                        ]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new template
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                                persistCurrentLocation();
                            });
                        }
                        //change on blur, focus
                        vm.editor.on('blur', persistCurrentLocation);
                        vm.editor.on('focus', persistCurrentLocation);
                        vm.editor.on('change', changeAceEditor);
                    }
                };
            }
            function insert(str) {
                vm.editor.focus();
                vm.editor.moveCursorToPosition(vm.currentPosition);
                vm.editor.insert(str);
                // set form state to $dirty
                setFormState('dirty');
            }
            function persistCurrentLocation() {
                vm.currentPosition = vm.editor.getCursorPosition();
            }
            function changeAceEditor() {
                setFormState('dirty');
            }
            function setFormState(state) {
                // get the current form
                var currentForm = angularHelper.getCurrentForm($scope);
                // set state
                if (state === 'dirty') {
                    currentForm.$setDirty();
                } else if (state === 'pristine') {
                    currentForm.$setPristine();
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.PartialViews.EditController', PartialViewsEditController);
    }());
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.BooleanController', function ($scope) {
        function updateToggleValue() {
            $scope.toggleValue = false;
            if ($scope.model && $scope.model.value && ($scope.model.value.toString() === '1' || angular.lowercase($scope.model.value) === 'true')) {
                $scope.toggleValue = true;
            }
        }
        if ($scope.model.value === null) {
            $scope.model.value = '0';
        }
        updateToggleValue();
        $scope.toggle = function () {
            if ($scope.model.value === 1 || $scope.model.value === '1') {
                $scope.model.value = '0';
                updateToggleValue();
                return;
            }
            $scope.model.value = '1';
            updateToggleValue();
        };
    });
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.ColorPickerController', function ($scope) {
        //setup the default config
        var config = { useLabel: false };
        //map the user config
        angular.extend(config, $scope.model.config);
        //map back to the model
        $scope.model.config = config;
        $scope.isConfigured = $scope.model.prevalues && _.keys($scope.model.prevalues).length > 0;
        $scope.model.items = [];
        // Make an array from the dictionary
        var items = [];
        if (angular.isArray($scope.model.prevalues)) {
            for (var i in $scope.model.prevalues) {
                var oldValue = $scope.model.prevalues[i];
                if (!isValidHex(oldValue.value || oldValue))
                    continue;
                if (oldValue.hasOwnProperty('value')) {
                    var hexCode = toFullHex(oldValue.value);
                    items.push({
                        value: hexCode.substr(1, hexCode.length),
                        label: oldValue.label,
                        id: i
                    });
                } else {
                    var hexCode = toFullHex(oldValue);
                    items.push({
                        value: hexCode.substr(1, hexCode.length),
                        label: oldValue,
                        id: i
                    });
                }
            }
            // Now make the editor model the array
            $scope.model.items = items;
        }
        function toFullHex(hex) {
            if (hex.length === 4 && hex.charAt(0) === '#') {
                hex = '#' + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2) + hex.charAt(3) + hex.charAt(3);
            }
            return hex.toLowerCase();
        }
        function isValidHex(str) {
            return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(str);
        }
    });
    function imageFilePickerController($scope) {
        $scope.add = function () {
            $scope.mediaPickerOverlay = {
                view: 'mediapicker',
                disableFolderSelect: true,
                onlyImages: true,
                show: true,
                submit: function (model) {
                    $scope.model.value = model.selectedImages[0].image;
                    $scope.mediaPickerOverlay.show = false;
                    $scope.mediaPickerOverlay = null;
                },
                close: function () {
                    $scope.mediaPickerOverlay.show = false;
                    $scope.mediaPickerOverlay = null;
                }
            };
        };
        $scope.remove = function () {
            $scope.model.value = null;
        };
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.ImageFilePickerController', imageFilePickerController);
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function mediaPickerController($scope, dialogService, entityResource, $log, iconHelper) {
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        $scope.renderModel = [];
        $scope.allowRemove = true;
        $scope.allowEdit = true;
        $scope.sortable = false;
        var dialogOptions = {
            multiPicker: false,
            entityType: 'Media',
            section: 'media',
            treeAlias: 'media',
            idType: 'int'
        };
        //combine the dialogOptions with any values returned from the server
        if ($scope.model.config) {
            angular.extend(dialogOptions, $scope.model.config);
        }
        $scope.openContentPicker = function () {
            $scope.contentPickerOverlay = dialogOptions;
            $scope.contentPickerOverlay.view = 'treePicker';
            $scope.contentPickerOverlay.show = true;
            $scope.contentPickerOverlay.submit = function (model) {
                if ($scope.contentPickerOverlay.multiPicker) {
                    _.each(model.selection, function (item, i) {
                        $scope.add(item);
                    });
                } else {
                    $scope.clear();
                    $scope.add(model.selection[0]);
                }
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
            $scope.contentPickerOverlay.close = function (oldModel) {
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
        };
        $scope.clear = function () {
            $scope.renderModel = [];
        };
        $scope.add = function (item) {
            var itemId = dialogOptions.idType === 'udi' ? item.udi : item.id;
            var currIds = _.map($scope.renderModel, function (i) {
                return dialogOptions.idType === 'udi' ? i.udi : i.id;
            });
            if (currIds.indexOf(itemId) < 0) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    icon: item.icon,
                    udi: item.udi
                });
                // store the index of the new item in the renderModel collection so we can find it again
                var itemRenderIndex = $scope.renderModel.length - 1;
                // get and update the path for the picked node
                entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) {
                    $scope.renderModel[itemRenderIndex].path = data;
                });
            }
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var currIds = _.map($scope.renderModel, function (i) {
                return dialogOptions.idType === 'udi' ? i.udi : i.id;
            });
            $scope.model.value = trim(currIds.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        //load media data
        var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
        if (modelIds.length > 0) {
            entityResource.getByIds(modelIds, dialogOptions.entityType).then(function (data) {
                _.each(data, function (item, i) {
                    item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                    $scope.renderModel.push({
                        name: item.name,
                        id: item.id,
                        icon: item.icon,
                        udi: item.udi
                    });
                    // store the index of the new item in the renderModel collection so we can find it again
                    var itemRenderIndex = $scope.renderModel.length - 1;
                    // get and update the path for the picked node
                    entityResource.getUrl(item.id, dialogOptions.entityType).then(function (data) {
                        $scope.renderModel[itemRenderIndex].path = data;
                    });
                });
            });
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MediaPickerController', mediaPickerController);
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiValuesController', function ($scope, $timeout) {
        //NOTE: We need to make each item an object, not just a string because you cannot 2-way bind to a primitive.
        $scope.newItem = '';
        $scope.hasError = false;
        $scope.focusOnNew = false;
        if (!angular.isArray($scope.model.value)) {
            //make an array from the dictionary
            var items = [];
            for (var i in $scope.model.value) {
                items.push({
                    value: $scope.model.value[i].value,
                    sortOrder: $scope.model.value[i].sortOrder,
                    id: i
                });
            }
            //ensure the items are sorted by the provided sort order
            items.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //now make the editor model the array
            $scope.model.value = items;
        }
        $scope.remove = function (item, evt) {
            evt.preventDefault();
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.value === item.value;
            });
        };
        $scope.add = function (evt) {
            evt.preventDefault();
            if ($scope.newItem) {
                if (!_.contains($scope.model.value, $scope.newItem)) {
                    $scope.model.value.push({ value: $scope.newItem });
                    $scope.newItem = '';
                    $scope.hasError = false;
                    $scope.focusOnNew = true;
                    return;
                }
            }
            //there was an error, do the highlight (will be set back by the directive)
            $scope.hasError = true;
        };
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> div.control-group',
            tolerance: 'pointer',
            update: function (e, ui) {
                // Get the new and old index for the moved element (using the text as the identifier, so 
                // we'd have a problem if two prevalues were the same, but that would be unlikely)
                var newIndex = ui.item.index();
                var movedPrevalueText = $('input[type="text"]', ui.item).val();
                var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
                // Move the element in the model
                if (originalIndex > -1) {
                    var movedElement = $scope.model.value[originalIndex];
                    $scope.model.value.splice(originalIndex, 1);
                    $scope.model.value.splice(newIndex, 0, movedElement);
                }
            }
        };
        $scope.createNew = function (event) {
            if (event.keyCode == 13) {
                $scope.add(event);
            }
        };
        function getElementIndexByPrevalueText(value) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].value === value) {
                    return i;
                }
            }
            return -1;
        }
    });
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreePickerController', function ($scope, dialogService, entityResource, $log, iconHelper) {
        $scope.renderModel = [];
        $scope.ids = [];
        $scope.allowRemove = true;
        $scope.allowEdit = true;
        $scope.sortable = false;
        var config = {
            multiPicker: false,
            entityType: 'Document',
            type: 'content',
            treeAlias: 'content',
            idType: 'int'
        };
        //combine the config with any values returned from the server
        if ($scope.model.config) {
            angular.extend(config, $scope.model.config);
        }
        if ($scope.model.value) {
            $scope.ids = $scope.model.value.split(',');
            entityResource.getByIds($scope.ids, config.entityType).then(function (data) {
                _.each(data, function (item, i) {
                    item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                    $scope.renderModel.push({
                        name: item.name,
                        id: item.id,
                        icon: item.icon,
                        udi: item.udi
                    });
                    // store the index of the new item in the renderModel collection so we can find it again
                    var itemRenderIndex = $scope.renderModel.length - 1;
                    // get and update the path for the picked node
                    entityResource.getUrl(item.id, config.entityType).then(function (data) {
                        $scope.renderModel[itemRenderIndex].path = data;
                    });
                });
            });
        }
        $scope.openContentPicker = function () {
            $scope.treePickerOverlay = config;
            $scope.treePickerOverlay.section = config.type;
            $scope.treePickerOverlay.view = 'treePicker';
            $scope.treePickerOverlay.show = true;
            $scope.treePickerOverlay.submit = function (model) {
                if (config.multiPicker) {
                    populate(model.selection);
                } else {
                    populate(model.selection[0]);
                }
                $scope.treePickerOverlay.show = false;
                $scope.treePickerOverlay = null;
            };
            $scope.treePickerOverlay.close = function (oldModel) {
                $scope.treePickerOverlay.show = false;
                $scope.treePickerOverlay = null;
            };
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
            $scope.ids.splice(index, 1);
            $scope.model.value = trim($scope.ids.join(), ',');
        };
        $scope.clear = function () {
            $scope.model.value = '';
            $scope.renderModel = [];
            $scope.ids = [];
        };
        $scope.add = function (item) {
            var itemId = config.idType === 'udi' ? item.udi : item.id;
            if ($scope.ids.indexOf(itemId) < 0) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.ids.push(itemId);
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    icon: item.icon,
                    udi: item.udi
                });
                $scope.model.value = trim($scope.ids.join(), ',');
                // store the index of the new item in the renderModel collection so we can find it again
                var itemRenderIndex = $scope.renderModel.length - 1;
                // get and update the path for the picked node
                entityResource.getUrl(item.id, config.entityType).then(function (data) {
                    $scope.renderModel[itemRenderIndex].path = data;
                });
            }
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            $scope.model.value = trim($scope.ids.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        function populate(data) {
            if (angular.isArray(data)) {
                _.each(data, function (item, i) {
                    $scope.add(item);
                });
            } else {
                $scope.clear();
                $scope.add(data);
            }
        }
    });
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.TreeSourceController', function ($scope, dialogService, entityResource, $log, iconHelper) {
        if (!$scope.model) {
            $scope.model = {};
        }
        if (!$scope.model.value) {
            $scope.model.value = { type: 'content' };
        }
        if (!$scope.model.config) {
            $scope.model.config = { idType: 'int' };
        }
        if ($scope.model.value.id && $scope.model.value.type !== 'member') {
            var ent = 'Document';
            if ($scope.model.value.type === 'media') {
                ent = 'Media';
            }
            entityResource.getById($scope.model.value.id, ent).then(function (item) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.node = item;
            });
        }
        $scope.openContentPicker = function () {
            $scope.treePickerOverlay = {
                view: 'treepicker',
                idType: $scope.model.config.idType,
                section: $scope.model.value.type,
                treeAlias: $scope.model.value.type,
                multiPicker: false,
                show: true,
                submit: function (model) {
                    var item = model.selection[0];
                    populate(item);
                    $scope.treePickerOverlay.show = false;
                    $scope.treePickerOverlay = null;
                }
            };
        };
        $scope.clear = function () {
            $scope.model.value.id = undefined;
            $scope.node = undefined;
            $scope.model.value.query = undefined;
        };
        //we always need to ensure we dont submit anything broken
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            if ($scope.model.value.type === 'member') {
                $scope.model.value.id = -1;
                $scope.model.value.query = '';
            }
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        function populate(item) {
            $scope.clear();
            item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            $scope.node = item;
            $scope.model.value.id = $scope.model.config.idType === 'udi' ? item.udi : item.id;
        }
    });
    function booleanEditorController($scope, angularHelper) {
        function setupViewModel() {
            $scope.renderModel = { value: false };
            if ($scope.model.config && $scope.model.config.default && $scope.model.config.default.toString() === '1' && $scope.model && !$scope.model.value) {
                $scope.renderModel.value = true;
            }
            if ($scope.model && $scope.model.value && ($scope.model.value.toString() === '1' || angular.lowercase($scope.model.value) === 'true')) {
                $scope.renderModel.value = true;
            }
        }
        setupViewModel();
        if ($scope.model && !$scope.model.value) {
            $scope.model.value = $scope.renderModel.value === true ? '1' : '0';
        }
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again if it has changed from the server
            setupViewModel();
        };
        // Update the value when the toggle is clicked
        $scope.toggle = function () {
            angularHelper.getCurrentForm($scope).$setDirty();
            if ($scope.renderModel.value) {
                $scope.model.value = '0';
                setupViewModel();
                return;
            }
            $scope.model.value = '1';
            setupViewModel();
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.BooleanController', booleanEditorController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ChangePasswordController', function ($scope, $routeParams) {
        $scope.isNew = $routeParams.create;
        function resetModel() {
            //the model config will contain an object, if it does not we'll create defaults
            //NOTE: We will not support doing the password regex on the client side because the regex on the server side
            //based on the membership provider cannot always be ported to js from .net directly.        
            /*
      {
          hasPassword: true/false,
          requiresQuestionAnswer: true/false,
          enableReset: true/false,
          enablePasswordRetrieval: true/false,
          minPasswordLength: 10
      }
      */
            //set defaults if they are not available
            if (!$scope.model.config || $scope.model.config.disableToggle === undefined) {
                $scope.model.config.disableToggle = false;
            }
            if (!$scope.model.config || $scope.model.config.hasPassword === undefined) {
                $scope.model.config.hasPassword = false;
            }
            if (!$scope.model.config || $scope.model.config.enablePasswordRetrieval === undefined) {
                $scope.model.config.enablePasswordRetrieval = true;
            }
            if (!$scope.model.config || $scope.model.config.requiresQuestionAnswer === undefined) {
                $scope.model.config.requiresQuestionAnswer = false;
            }
            if (!$scope.model.config || $scope.model.config.enableReset === undefined) {
                $scope.model.config.enableReset = true;
            }
            if (!$scope.model.config || $scope.model.config.minPasswordLength === undefined) {
                $scope.model.config.minPasswordLength = 0;
            }
            //set the model defaults
            if (!angular.isObject($scope.model.value)) {
                //if it's not an object then just create a new one
                $scope.model.value = {
                    newPassword: null,
                    oldPassword: null,
                    reset: null,
                    answer: null
                };
            } else {
                //just reset the values
                if (!$scope.isNew) {
                    //if it is new, then leave the generated pass displayed
                    $scope.model.value.newPassword = null;
                    $scope.model.value.oldPassword = null;
                }
                $scope.model.value.reset = null;
                $scope.model.value.answer = null;
            }
        }
        resetModel();
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.CheckboxListController', function ($scope) {
        if (angular.isObject($scope.model.config.items)) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            var vals = _.values($scope.model.config.items);
            var keys = _.keys($scope.model.config.items);
            for (var i = 0; i < vals.length; i++) {
                newItems.push({
                    id: keys[i],
                    sortOrder: vals[i].sortOrder,
                    value: vals[i].value
                });
            }
            //ensure the items are sorted by the provided sort order
            newItems.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //re-assign
            $scope.model.config.items = newItems;
        }
        function setupViewModel() {
            $scope.selectedItems = [];
            //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
            // to "" gets selected by default
            if ($scope.model.value === null || $scope.model.value === undefined) {
                $scope.model.value = [];
            }
            for (var i = 0; i < $scope.model.config.items.length; i++) {
                var isChecked = _.contains($scope.model.value, $scope.model.config.items[i].id);
                $scope.selectedItems.push({
                    checked: isChecked,
                    key: $scope.model.config.items[i].id,
                    val: $scope.model.config.items[i].value
                });
            }
        }
        setupViewModel();
        //update the model when the items checked changes
        $scope.$watch('selectedItems', function (newVal, oldVal) {
            $scope.model.value = [];
            for (var x = 0; x < $scope.selectedItems.length; x++) {
                if ($scope.selectedItems[x].checked) {
                    $scope.model.value.push($scope.selectedItems[x].key);
                }
            }
        }, true);
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again if it has changed from the server
            setupViewModel();
        };
    });
    function ColorPickerController($scope, angularHelper) {
        //setup the default config
        var config = {
            items: [],
            multiple: false
        };
        //map the user config
        angular.extend(config, $scope.model.config);
        //map back to the model
        $scope.model.config = config;
        $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
        $scope.model.activeColor = {
            value: '',
            label: ''
        };
        if ($scope.isConfigured) {
            for (var key in $scope.model.config.items) {
                if (!$scope.model.config.items[key].hasOwnProperty('value'))
                    $scope.model.config.items[key] = {
                        value: $scope.model.config.items[key],
                        label: $scope.model.config.items[key]
                    };
            }
            $scope.model.useLabel = isTrue($scope.model.config.useLabel);
            initActiveColor();
        }
        if (!angular.isArray($scope.model.config.items)) {
            //make an array from the dictionary
            var items = [];
            for (var i in $scope.model.config.items) {
                var oldValue = $scope.model.config.items[i];
                if (oldValue.hasOwnProperty('value')) {
                    items.push({
                        value: oldValue.value,
                        label: oldValue.label,
                        sortOrder: oldValue.sortOrder,
                        id: i
                    });
                } else {
                    items.push({
                        value: oldValue,
                        label: oldValue,
                        sortOrder: sortOrder,
                        id: i
                    });
                }
            }
            //ensure the items are sorted by the provided sort order
            items.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //now make the editor model the array
            $scope.model.config.items = items;
        }
        // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected)
        $scope.validateMandatory = function () {
            var isValid = !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value != '' && (!$scope.model.value.hasOwnProperty('value') || $scope.model.value.value !== '');
            return {
                isValid: isValid,
                errorMsg: 'Value cannot be empty',
                errorKey: 'required'
            };
        };
        $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0;
        $scope.onSelect = function (color) {
            // did the value change?
            if ($scope.model.value != null && $scope.model.value.value === color) {
                // User clicked the currently selected color
                // to remove the selection, they don't want
                // to select any color after all.
                // Unselect the color and mark as dirty
                $scope.model.activeColor = null;
                $scope.model.value = null;
                angularHelper.getCurrentForm($scope).$setDirty();
                return;
            }
            // yes, update the model (label + value) according to the new color
            var selectedItem = _.find($scope.model.config.items, function (item) {
                return item.value === color;
            });
            if (!selectedItem) {
                return;
            }
            $scope.model.value = {
                label: selectedItem.label,
                value: selectedItem.value
            };
            // make sure to set dirty
            angularHelper.getCurrentForm($scope).$setDirty();
        };
        // Finds the color best matching the model's color,
        // and sets the model color to that one. This is useful when
        // either the value or label was changed on the data type.
        function initActiveColor() {
            // no value - initialize default value
            if (!$scope.model.value)
                return;
            // Backwards compatibility, the color used to be stored as a hex value only
            if (typeof $scope.model.value === 'string') {
                $scope.model.value = {
                    value: $scope.model.value,
                    label: $scope.model.value
                };
            }
            var modelColor = $scope.model.value.value;
            var modelLabel = $scope.model.value.label;
            // Check for a full match or partial match.
            var foundItem = null;
            // Look for a fully matching color.
            for (var key in $scope.model.config.items) {
                var item = $scope.model.config.items[key];
                if (item.value == modelColor && item.label == modelLabel) {
                    foundItem = item;
                    break;
                }
            }
            // Look for a color with a matching value.
            if (!foundItem) {
                for (var key in $scope.model.config.items) {
                    var item = $scope.model.config.items[key];
                    if (item.value == modelColor) {
                        foundItem = item;
                        break;
                    }
                }
            }
            // Look for a color with a matching label.
            if (!foundItem) {
                for (var key in $scope.model.config.items) {
                    var item = $scope.model.config.items[key];
                    if (item.label == modelLabel) {
                        foundItem = item;
                        break;
                    }
                }
            }
            // If a match was found, set it as the active color.
            if (foundItem) {
                $scope.model.activeColor.value = foundItem.value;
                $scope.model.activeColor.label = foundItem.label;
            }
        }
        // figures out if a value is trueish enough
        function isTrue(bool) {
            return !!bool && bool !== '0' && angular.lowercase(bool) !== 'false';
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ColorPickerController', ColorPickerController);
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MultiColorPickerController', function ($scope, $timeout, assetsService, angularHelper, $element, localizationService, eventsService) {
        //NOTE: We need to make each color an object, not just a string because you cannot 2-way bind to a primitive.
        var defaultColor = '000000';
        var defaultLabel = null;
        $scope.newColor = defaultColor;
        $scope.newLabel = defaultLabel;
        $scope.hasError = false;
        $scope.focusOnNew = false;
        $scope.labels = {};
        var labelKeys = [
            'general_cancel',
            'general_choose'
        ];
        $scope.labelEnabled = false;
        eventsService.on('toggleValue', function (e, args) {
            $scope.labelEnabled = args.value;
        });
        localizationService.localizeMany(labelKeys).then(function (values) {
            $scope.labels.cancel = values[0];
            $scope.labels.choose = values[1];
        });
        assetsService.load([//"lib/spectrum/tinycolor.js",
            'lib/spectrum/spectrum.js'], $scope).then(function () {
            var elem = $element.find('input[name=\'newColor\']');
            elem.spectrum({
                color: null,
                showInitial: false,
                chooseText: $scope.labels.choose,
                cancelText: $scope.labels.cancel,
                preferredFormat: 'hex',
                showInput: true,
                clickoutFiresChange: true,
                hide: function (color) {
                    //show the add butotn
                    $element.find('.btn.add').show();
                },
                change: function (color) {
                    angularHelper.safeApply($scope, function () {
                        $scope.newColor = color.toHexString().trimStart('#');    // #ff0000
                    });
                },
                show: function () {
                    //hide the add butotn
                    $element.find('.btn.add').hide();
                }
            });
        });
        if (!angular.isArray($scope.model.value)) {
            //make an array from the dictionary
            var items = [];
            for (var i in $scope.model.value) {
                var oldValue = $scope.model.value[i];
                if (oldValue.hasOwnProperty('value')) {
                    items.push({
                        value: oldValue.value,
                        label: oldValue.label,
                        sortOrder: oldValue.sortOrder,
                        id: i
                    });
                } else {
                    items.push({
                        value: oldValue,
                        label: oldValue,
                        sortOrder: sortOrder,
                        id: i
                    });
                }
            }
            //ensure the items are sorted by the provided sort order
            items.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //now make the editor model the array
            $scope.model.value = items;
        }
        // ensure labels
        for (var i = 0; i < $scope.model.value.length; i++) {
            var item = $scope.model.value[i];
            item.label = item.hasOwnProperty('label') ? item.label : item.value;
        }
        function validLabel(label) {
            return label !== null && typeof label !== 'undefined' && label !== '' && label.length && label.length > 0;
        }
        $scope.remove = function (item, evt) {
            evt.preventDefault();
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.value === item.value && x.label === item.label;
            });
        };
        $scope.add = function (evt) {
            evt.preventDefault();
            if ($scope.newColor) {
                var newLabel = validLabel($scope.newLabel) ? $scope.newLabel : $scope.newColor;
                var exists = _.find($scope.model.value, function (item) {
                    return item.value.toUpperCase() === $scope.newColor.toUpperCase() || item.label.toUpperCase() === newLabel.toUpperCase();
                });
                if (!exists) {
                    $scope.model.value.push({
                        value: $scope.newColor,
                        label: newLabel
                    });
                    $scope.newLabel = '';
                    $scope.hasError = false;
                    $scope.focusOnNew = true;
                    return;
                }
                //there was an error, do the highlight (will be set back by the directive)
                $scope.hasError = true;
            }
        };
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            //handle: ".handle, .thumbnail",
            items: '> div.control-group',
            tolerance: 'pointer',
            update: function (e, ui) {
                // Get the new and old index for the moved element (using the text as the identifier, so 
                // we'd have a problem if two prevalues were the same, but that would be unlikely)
                var newIndex = ui.item.index();
                var movedPrevalueText = $('pre', ui.item).text();
                var originalIndex = getElementIndexByPrevalueText(movedPrevalueText);
                //// Move the element in the model
                if (originalIndex > -1) {
                    var movedElement = $scope.model.value[originalIndex];
                    $scope.model.value.splice(originalIndex, 1);
                    $scope.model.value.splice(newIndex, 0, movedElement);
                }
            }
        };
        function getElementIndexByPrevalueText(value) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].value === value) {
                    return i;
                }
            }
            return -1;
        }
        //load the separate css for the editor to avoid it blocking our js loading
        assetsService.loadCss('lib/spectrum/spectrum.css', $scope);
    });
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) {
        var unsubscribe;
        function subscribe() {
            unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                var currIds = _.map($scope.renderModel, function (i) {
                    return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                });
                $scope.model.value = trim(currIds.join(), ',');
            });
        }
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        function startWatch() {
            //We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
            // because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
            // occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
            // In their source code there is no event so we need to just subscribe to our model changes here.
            //This also makes it easier to manage models, we update one and the rest will just work.
            $scope.$watch(function () {
                //return the joined Ids as a string to watch
                return _.map($scope.renderModel, function (i) {
                    return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                }).join();
            }, function (newVal) {
                var currIds = _.map($scope.renderModel, function (i) {
                    return $scope.model.config.idType === 'udi' ? i.udi : i.id;
                });
                $scope.model.value = trim(currIds.join(), ',');
                //Validate!
                if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
                    $scope.contentPickerForm.minCount.$setValidity('minCount', false);
                } else {
                    $scope.contentPickerForm.minCount.$setValidity('minCount', true);
                }
                if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
                    $scope.contentPickerForm.maxCount.$setValidity('maxCount', false);
                } else {
                    $scope.contentPickerForm.maxCount.$setValidity('maxCount', true);
                }
                setSortingState($scope.renderModel);
            });
        }
        $scope.renderModel = [];
        $scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
        //the default pre-values
        var defaultConfig = {
            multiPicker: false,
            showOpenButton: false,
            showEditButton: false,
            showPathOnHover: false,
            maxNumber: 1,
            minNumber: 0,
            startNode: {
                query: '',
                type: 'content',
                id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1    // get start node for simple Content Picker
            }
        };
        // sortable options
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            distance: 10,
            opacity: 0.7,
            tolerance: 'pointer',
            scroll: true,
            zIndex: 6000,
            update: function (e, ui) {
                angularHelper.getCurrentForm($scope).$setDirty();
            }
        };
        if ($scope.model.config) {
            //merge the server config on top of the default config, then set the server config to use the result
            $scope.model.config = angular.extend(defaultConfig, $scope.model.config);
        }
        //Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
        $scope.model.config.multiPicker = $scope.model.config.multiPicker === '1' ? true : false;
        $scope.model.config.showOpenButton = $scope.model.config.showOpenButton === '1' ? true : false;
        $scope.model.config.showEditButton = $scope.model.config.showEditButton === '1' ? true : false;
        $scope.model.config.showPathOnHover = $scope.model.config.showPathOnHover === '1' ? true : false;
        var entityType = $scope.model.config.startNode.type === 'member' ? 'Member' : $scope.model.config.startNode.type === 'media' ? 'Media' : 'Document';
        $scope.allowOpenButton = entityType === 'Document';
        $scope.allowEditButton = entityType === 'Document';
        $scope.allowRemoveButton = true;
        //the dialog options for the picker
        var dialogOptions = {
            multiPicker: $scope.model.config.multiPicker,
            entityType: entityType,
            filterCssClass: 'not-allowed not-published',
            startNodeId: null,
            currentNode: editorState ? editorState.current : null,
            callback: function (data) {
                if (angular.isArray(data)) {
                    _.each(data, function (item, i) {
                        $scope.add(item);
                    });
                } else {
                    $scope.clear();
                    $scope.add(data);
                }
                angularHelper.getCurrentForm($scope).$setDirty();
            },
            treeAlias: $scope.model.config.startNode.type,
            section: $scope.model.config.startNode.type,
            idType: 'int'
        };
        //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the 
        // pre-value config on to the dialog options
        angular.extend(dialogOptions, $scope.model.config);
        //We need to manually handle the filter for members here since the tree displayed is different and only contains
        // searchable list views
        if (entityType === 'Member') {
            //first change the not allowed filter css class
            dialogOptions.filterCssClass = 'not-allowed';
            var currFilter = dialogOptions.filter;
            //now change the filter to be a method
            dialogOptions.filter = function (i) {
                //filter out the list view nodes
                if (i.metaData.isContainer) {
                    return true;
                }
                if (!currFilter) {
                    return false;
                }
                //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, 
                // but not much we can do about that since members require special filtering.
                var filterItem = currFilter.toLowerCase().split(',');
                var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
                if (!currFilter.startsWith('!') && !found || currFilter.startsWith('!') && found) {
                    return true;
                }
                return false;
            };
        }
        if ($routeParams.section === 'settings' && $routeParams.tree === 'documentTypes') {
            //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query
            dialogOptions.startNodeId = -1;
        } else if ($scope.model.config.startNode.query) {
            //if we have a query for the startnode, we will use that.
            var rootId = $routeParams.id;
            entityResource.getByQuery($scope.model.config.startNode.query, rootId, 'Document').then(function (ent) {
                dialogOptions.startNodeId = $scope.model.config.idType === 'udi' ? ent.udi : ent.id;
            });
        } else {
            dialogOptions.startNodeId = $scope.model.config.startNode.id;
        }
        //dialog
        $scope.openContentPicker = function () {
            $scope.contentPickerOverlay = dialogOptions;
            $scope.contentPickerOverlay.view = 'treepicker';
            $scope.contentPickerOverlay.show = true;
            $scope.contentPickerOverlay.submit = function (model) {
                if (angular.isArray(model.selection)) {
                    _.each(model.selection, function (item, i) {
                        $scope.add(item);
                    });
                    angularHelper.getCurrentForm($scope).$setDirty();
                }
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
            $scope.contentPickerOverlay.close = function (oldModel) {
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
            angularHelper.getCurrentForm($scope).$setDirty();
        };
        $scope.showNode = function (index) {
            var item = $scope.renderModel[index];
            var id = item.id;
            var section = $scope.model.config.startNode.type.toLowerCase();
            entityResource.getPath(id, entityType).then(function (path) {
                navigationService.changeSection(section);
                navigationService.showTree(section, {
                    tree: section,
                    path: path,
                    forceReload: false,
                    activate: true
                });
                var routePath = section + '/' + section + '/edit/' + id.toString();
                $location.path(routePath).search('');
            });
        };
        $scope.add = function (item) {
            var currIds = _.map($scope.renderModel, function (i) {
                return $scope.model.config.idType === 'udi' ? i.udi : i.id;
            });
            var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id;
            if (currIds.indexOf(itemId) < 0) {
                setEntityUrl(item);
            }
        };
        $scope.clear = function () {
            $scope.renderModel = [];
        };
        $scope.openMiniEditor = function (node) {
            miniEditorHelper.launchMiniEditor(node).then(function (updatedNode) {
                // update the node
                node.name = updatedNode.name;
                node.published = updatedNode.hasPublishedVersion;
                if (entityType !== 'Member') {
                    entityResource.getUrl(updatedNode.id, entityType).then(function (data) {
                        node.url = data;
                    });
                }
            });
        };
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            if (unsubscribe) {
                unsubscribe();
            }
        });
        var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
        //load current data if anything selected
        if (modelIds.length > 0) {
            entityResource.getByIds(modelIds, entityType).then(function (data) {
                _.each(modelIds, function (id, i) {
                    var entity = _.find(data, function (d) {
                        return $scope.model.config.idType === 'udi' ? d.udi == id : d.id == id;
                    });
                    if (entity) {
                        setEntityUrl(entity);
                    }
                });
                //everything is loaded, start the watch on the model
                startWatch();
                subscribe();
            });
        } else {
            //everything is loaded, start the watch on the model
            startWatch();
            subscribe();
        }
        function setEntityUrl(entity) {
            // get url for content and media items
            if (entityType !== 'Member') {
                entityResource.getUrl(entity.id, entityType).then(function (data) {
                    // update url                
                    angular.forEach($scope.renderModel, function (item) {
                        if (item.id === entity.id) {
                            if (entity.trashed) {
                                item.url = localizationService.dictionary.general_recycleBin;
                            } else {
                                item.url = data;
                            }
                        }
                    });
                });
            }
            // add the selected item to the renderModel
            // if it needs to show a url the item will get 
            // updated when the url comes back from server
            addSelectedItem(entity);
        }
        function addSelectedItem(item) {
            // set icon
            if (item.icon) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
            }
            // set default icon
            if (!item.icon) {
                switch (entityType) {
                case 'Document':
                    item.icon = 'icon-document';
                    break;
                case 'Media':
                    item.icon = 'icon-picture';
                    break;
                case 'Member':
                    item.icon = 'icon-user';
                    break;
                }
            }
            $scope.renderModel.push({
                'name': item.name,
                'id': item.id,
                'udi': item.udi,
                'icon': item.icon,
                'path': item.path,
                'url': item.url,
                'trashed': item.trashed,
                'published': item.metaData && item.metaData.IsPublished === false && entityType === 'Document' ? false : true    // only content supports published/unpublished content so we set everything else to published so the UI looks correct 
            });
        }
        function setSortingState(items) {
            // disable sorting if the list only consist of one item
            if (items.length > 1) {
                $scope.sortableOptions.disabled = false;
            } else {
                $scope.sortableOptions.disabled = true;
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ContentPickerController', contentPickerController);
    function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) {
        //setup the default config
        var config = {
            pickDate: true,
            pickTime: true,
            useSeconds: true,
            format: 'YYYY-MM-DD HH:mm:ss',
            icons: {
                time: 'icon-time',
                date: 'icon-calendar',
                up: 'icon-chevron-up',
                down: 'icon-chevron-down'
            }
        };
        //map the user config
        $scope.model.config = angular.extend(config, $scope.model.config);
        //ensure the format doesn't get overwritten with an empty string
        if ($scope.model.config.format === '' || $scope.model.config.format === undefined || $scope.model.config.format === null) {
            $scope.model.config.format = $scope.model.config.pickTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
        }
        $scope.hasDatetimePickerValue = $scope.model.value ? true : false;
        $scope.datetimePickerValue = null;
        //hide picker if clicking on the document 
        $scope.hidePicker = function () {
            //$element.find("div:first").datetimepicker("hide");
            // Sometimes the statement above fails and generates errors in the browser console. The following statements fix that.
            var dtp = $element.find('div:first');
            if (dtp && dtp.datetimepicker) {
                dtp.datetimepicker('hide');
            }
        };
        $(document).bind('click', $scope.hidePicker);
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            if (newVal != oldVal) {
                //check for c# System.DateTime.MinValue being passed as the clear indicator
                var minDate = moment('0001-01-01');
                var newDate = moment(newVal);
                if (newDate.isAfter(minDate)) {
                    applyDate({ date: moment(newVal) });
                } else {
                    $scope.clearDate();
                }
            }
        };
        //handles the date changing via the api
        function applyDate(e) {
            angularHelper.safeApply($scope, function () {
                // when a date is changed, update the model
                if (e.date && e.date.isValid()) {
                    $scope.datePickerForm.datepicker.$setValidity('pickerError', true);
                    $scope.hasDatetimePickerValue = true;
                    $scope.datetimePickerValue = e.date.format($scope.model.config.format);
                } else {
                    $scope.hasDatetimePickerValue = false;
                    $scope.datetimePickerValue = null;
                }
                setModelValue();
                if (!$scope.model.config.pickTime) {
                    $element.find('div:first').datetimepicker('hide', 0);
                }
            });
        }
        //sets the scope model value accordingly - this is the value to be sent up to the server and depends on 
        // if the picker is configured to offset time. We always format the date/time in a specific format for sending
        // to the server, this is different from the format used to display the date/time.
        function setModelValue() {
            if ($scope.hasDatetimePickerValue) {
                var elementData = $element.find('div:first').data().DateTimePicker;
                if ($scope.model.config.pickTime) {
                    //check if we are supposed to offset the time
                    if ($scope.model.value && $scope.model.config.offsetTime === '1' && Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
                        $scope.model.value = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset);
                        $scope.serverTime = dateHelper.convertToServerStringTime(elementData.getDate(), Umbraco.Sys.ServerVariables.application.serverTimeOffset, 'YYYY-MM-DD HH:mm:ss Z');
                    } else {
                        $scope.model.value = elementData.getDate().format('YYYY-MM-DD HH:mm:ss');
                    }
                } else {
                    $scope.model.value = elementData.getDate().format('YYYY-MM-DD');
                }
            } else {
                $scope.model.value = null;
            }
        }
        var picker = null;
        $scope.clearDate = function () {
            $scope.hasDatetimePickerValue = false;
            $scope.datetimePickerValue = null;
            $scope.model.value = null;
            $scope.datePickerForm.datepicker.$setValidity('pickerError', true);
        };
        $scope.serverTime = null;
        $scope.serverTimeNeedsOffsetting = false;
        if (Umbraco.Sys.ServerVariables.application.serverTimeOffset !== undefined) {
            // Will return something like 120
            var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
            // Will return something like -120
            var localOffset = new Date().getTimezoneOffset();
            // If these aren't equal then offsetting is needed
            // note the minus in front of serverOffset needed 
            // because C# and javascript return the inverse offset
            $scope.serverTimeNeedsOffsetting = -serverOffset !== localOffset;
        }
        //get the current user to see if we can localize this picker
        userService.getCurrentUser().then(function (user) {
            assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css', $scope).then(function () {
                var filesToLoad = ['lib/datetimepicker/bootstrap-datetimepicker.js'];
                $scope.model.config.language = user.locale;
                assetsService.load(filesToLoad, $scope).then(function () {
                    //The Datepicker js and css files are available and all components are ready to use.
                    // Get the id of the datepicker button that was clicked
                    var pickerId = $scope.model.alias;
                    var element = $element.find('div:first');
                    // Open the datepicker and add a changeDate eventlistener
                    element.datetimepicker(angular.extend({ useCurrent: $scope.model.config.defaultEmpty !== '1' }, $scope.model.config)).on('dp.change', applyDate).on('dp.error', function (a, b, c) {
                        $scope.hasDatetimePickerValue = false;
                        $scope.datePickerForm.datepicker.$setValidity('pickerError', false);
                    });
                    if ($scope.hasDatetimePickerValue) {
                        var dateVal;
                        //check if we are supposed to offset the time
                        if ($scope.model.value && $scope.model.config.offsetTime === '1' && $scope.serverTimeNeedsOffsetting) {
                            //get the local time offset from the server
                            dateVal = dateHelper.convertToLocalMomentTime($scope.model.value, Umbraco.Sys.ServerVariables.application.serverTimeOffset);
                            $scope.serverTime = dateHelper.convertToServerStringTime(dateVal, Umbraco.Sys.ServerVariables.application.serverTimeOffset, 'YYYY-MM-DD HH:mm:ss Z');
                        } else {
                            //create a normal moment , no offset required
                            var dateVal = $scope.model.value ? moment($scope.model.value, 'YYYY-MM-DD HH:mm:ss') : moment();
                        }
                        element.datetimepicker('setValue', dateVal);
                        $scope.datetimePickerValue = dateVal.format($scope.model.config.format);
                    }
                    element.find('input').bind('blur', function () {
                        //we need to force an apply here
                        $scope.$apply();
                    });
                    //Ensure to remove the event handler when this instance is destroyted
                    $scope.$on('$destroy', function () {
                        element.find('input').unbind('blur');
                        element.datetimepicker('destroy');
                    });
                    var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                        setModelValue();
                    });
                    //unbind doc click event!
                    $scope.$on('$destroy', function () {
                        unsubscribe();
                    });
                });
            });
        });
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            setModelValue();
        });
        //unbind doc click event!
        $scope.$on('$destroy', function () {
            $(document).unbind('click', $scope.hidePicker);
            unsubscribe();
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.DatepickerController', dateTimePickerController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownController', function ($scope) {
        //setup the default config
        var config = {
            items: [],
            multiple: false
        };
        //map the user config
        angular.extend(config, $scope.model.config);
        //map back to the model
        $scope.model.config = config;
        function convertArrayToDictionaryArray(model) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            for (var i = 0; i < model.length; i++) {
                newItems.push({
                    id: model[i],
                    sortOrder: 0,
                    value: model[i]
                });
            }
            return newItems;
        }
        function convertObjectToDictionaryArray(model) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            var vals = _.values($scope.model.config.items);
            var keys = _.keys($scope.model.config.items);
            for (var i = 0; i < vals.length; i++) {
                var label = vals[i].value ? vals[i].value : vals[i];
                newItems.push({
                    id: keys[i],
                    sortOrder: vals[i].sortOrder,
                    value: label
                });
            }
            return newItems;
        }
        if (angular.isArray($scope.model.config.items)) {
            //PP: I dont think this will happen, but we have tests that expect it to happen..
            //if array is simple values, convert to array of objects
            if (!angular.isObject($scope.model.config.items[0])) {
                $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
            }
        } else if (angular.isObject($scope.model.config.items)) {
            $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
        } else {
            throw 'The items property must be either an array or a dictionary';
        }
        //sort the values
        $scope.model.config.items.sort(function (a, b) {
            return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
        });
        //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
        // to "" gets selected by default
        if ($scope.model.value === null || $scope.model.value === undefined) {
            if ($scope.model.config.multiple) {
                $scope.model.value = [];
            } else {
                $scope.model.value = '';
            }
        }
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.DropdownFlexibleController', function ($scope) {
        //setup the default config
        var config = {
            items: [],
            multiple: false
        };
        //map the user config
        angular.extend(config, $scope.model.config);
        //map back to the model
        $scope.model.config = config;
        function convertArrayToDictionaryArray(model) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            for (var i = 0; i < model.length; i++) {
                newItems.push({
                    id: model[i],
                    sortOrder: 0,
                    value: model[i]
                });
            }
            return newItems;
        }
        function convertObjectToDictionaryArray(model) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            var vals = _.values($scope.model.config.items);
            var keys = _.keys($scope.model.config.items);
            for (var i = 0; i < vals.length; i++) {
                var label = vals[i].value ? vals[i].value : vals[i];
                newItems.push({
                    id: keys[i],
                    sortOrder: vals[i].sortOrder,
                    value: label
                });
            }
            return newItems;
        }
        $scope.updateSingleDropdownValue = function () {
            $scope.model.value = [$scope.model.singleDropdownValue];
        };
        if (angular.isArray($scope.model.config.items)) {
            //PP: I dont think this will happen, but we have tests that expect it to happen..
            //if array is simple values, convert to array of objects
            if (!angular.isObject($scope.model.config.items[0])) {
                $scope.model.config.items = convertArrayToDictionaryArray($scope.model.config.items);
            }
        } else if (angular.isObject($scope.model.config.items)) {
            $scope.model.config.items = convertObjectToDictionaryArray($scope.model.config.items);
        } else {
            throw 'The items property must be either an array or a dictionary';
        }
        //sort the values
        $scope.model.config.items.sort(function (a, b) {
            return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
        });
        //now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
        // to "" gets selected by default
        if ($scope.model.value === null || $scope.model.value === undefined) {
            if ($scope.model.config.multiple) {
                $scope.model.value = [];
            } else {
                $scope.model.value = '';
            }
        }
        // if we run in single mode we'll store the value in a local variable
        // so we can pass an array as the model as our PropertyValueEditor expects that
        $scope.model.singleDropdownValue = '';
        if ($scope.model.config.multiple === '0' && $scope.model.value) {
            $scope.model.singleDropdownValue = Array.isArray($scope.model.value) ? $scope.model.value[0] : $scope.model.value;
        }
        // if we run in multiple mode, make sure the model is an array (in case the property was previously saved in single mode)
        // also explicitly set the model to null if it's an empty array, so mandatory validation works on the client
        if ($scope.model.config.multiple === '1' && $scope.model.value) {
            $scope.model.value = !Array.isArray($scope.model.value) ? [$scope.model.value] : $scope.model.value;
            if ($scope.model.value.length === 0) {
                $scope.model.value = null;
            }
        }
    });
    /** A drop down list or multi value select list based on an entity type, this can be re-used for any entity types */
    function entityPicker($scope, entityResource) {
        //set the default to DocumentType
        if (!$scope.model.config.entityType) {
            $scope.model.config.entityType = 'DocumentType';
        }
        //Determine the select list options and which value to publish
        if (!$scope.model.config.publishBy) {
            $scope.selectOptions = 'entity.id as entity.name for entity in entities';
        } else {
            $scope.selectOptions = 'entity.' + $scope.model.config.publishBy + ' as entity.name for entity in entities';
        }
        entityResource.getAll($scope.model.config.entityType).then(function (data) {
            //convert the ids to strings so the drop downs work properly when comparing
            _.each(data, function (d) {
                d.id = d.id.toString();
            });
            $scope.entities = data;
        });
        if ($scope.model.value === null || $scope.model.value === undefined) {
            if ($scope.model.config.multiple) {
                $scope.model.value = [];
            } else {
                $scope.model.value = '';
            }
        } else {
            //if it's multiple, change the value to an array
            if ($scope.model.config.multiple === '1') {
                if (_.isString($scope.model.value)) {
                    $scope.model.value = $scope.model.value.split(',');
                }
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.EntityPickerController', entityPicker);
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.FileUploadController
 * @function
 *
 * @description
 * The controller for the file upload property editor. It is important to note that the $scope.model.value
 *  doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we
 *  are submitting files because in that case, we are adding files to the fileManager which is what gets peristed
 *  on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}"
 *  to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to
 *  be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows
 *  for the editors to check if the value has changed and to re-bind the property if that is true.
 *
*/
    function fileUploadController($scope, $element, $compile, imageHelper, fileManager, umbRequestHelper, mediaHelper) {
        /** Clears the file collections when content is saving (if we need to clear) or after saved */
        function clearFiles() {
            //clear the files collection (we don't want to upload any!)
            fileManager.setFiles($scope.model.alias, []);
            //clear the current files
            $scope.files = [];
            if ($scope.propertyForm) {
                if ($scope.propertyForm.fileCount) {
                    //this is required to re-validate
                    $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
                }
            }
        }
        /** this method is used to initialize the data and to re-initialize it if the server value is changed */
        function initialize(index) {
            clearFiles();
            if (!index) {
                index = 1;
            }
            //this is used in order to tell the umb-single-file-upload directive to
            //rebuild the html input control (and thus clearing the selected file) since
            //that is the only way to manipulate the html for the file input control.
            $scope.rebuildInput = { index: index };
            //clear the current files
            $scope.files = [];
            //store the original value so we can restore it if the user clears and then cancels clearing.
            $scope.originalValue = $scope.model.value;
            //create the property to show the list of files currently saved
            if ($scope.model.value != '' && $scope.model.value != undefined) {
                var images = $scope.model.value.split(',');
                $scope.persistedFiles = _.map(images, function (item) {
                    return {
                        file: item,
                        isImage: imageHelper.detectIfImageByExtension(item)
                    };
                });
            } else {
                $scope.persistedFiles = [];
            }
            _.each($scope.persistedFiles, function (file) {
                var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: file.file }]);
                var extension = file.file.substring(file.file.lastIndexOf('.') + 1, file.file.length);
                file.thumbnail = thumbnailUrl + '&rnd=' + Math.random();
                file.extension = extension.toLowerCase();
            });
            $scope.clearFiles = false;
        }
        initialize();
        // Method required by the valPropertyValidator directive (returns true if the property editor has at least one file selected)
        $scope.validateMandatory = function () {
            return {
                isValid: !$scope.model.validation.mandatory || ($scope.persistedFiles != null && $scope.persistedFiles.length > 0 || $scope.files != null && $scope.files.length > 0) && !$scope.clearFiles,
                errorMsg: 'Value cannot be empty',
                errorKey: 'required'
            };
        };
        //listen for clear files changes to set our model to be sent up to the server
        $scope.$watch('clearFiles', function (isCleared) {
            if (isCleared == true) {
                $scope.model.value = { clearFiles: true };
                clearFiles();
            } else {
                //reset to original value
                $scope.model.value = $scope.originalValue;
                //this is required to re-validate
                if ($scope.propertyForm) {
                    $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
                }
            }
        });
        //listen for when a file is selected
        $scope.$on('filesSelected', function (event, args) {
            $scope.$apply(function () {
                //set the files collection
                fileManager.setFiles($scope.model.alias, args.files);
                //clear the current files
                $scope.files = [];
                var newVal = '';
                for (var i = 0; i < args.files.length; i++) {
                    //save the file object to the scope's files collection
                    $scope.files.push({
                        alias: $scope.model.alias,
                        file: args.files[i]
                    });
                    newVal += args.files[i].name.split(',').join('-') + ',';
                }
                //this is required to re-validate
                $scope.propertyForm.fileCount.$setViewValue($scope.files.length);
                //set clear files to false, this will reset the model too
                $scope.clearFiles = false;
                //set the model value to be the concatenation of files selected. Please see the notes
                // in the description of this controller, it states that this value isn't actually used for persistence,
                // but we need to set it so that the editor and the server can detect that it's been changed, and it is used for validation.
                $scope.model.value = { selectedFiles: newVal.trimEnd(',') };
                //need to explicity setDirty here as file upload field can't track dirty & we can't use the fileCount (hidden field/model)
                $scope.propertyForm.$setDirty();
            });
        });
        //listen for when the model value has changed
        $scope.$watch('model.value', function (newVal, oldVal) {
            //cannot just check for !newVal because it might be an empty string which we
            //want to look for.
            if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                // here we need to check if the value change needs to trigger an update in the UI.
                // if the value is only changed in the controller and not in the server values, we do not
                // want to trigger an update yet.
                // we can however no longer rely on checking values in the controller vs. values from the server
                // to determine whether to update or not, since you could potentially be uploading a file with
                // the exact same name - in that case we need to reinitialize to show the newly uploaded file.
                if (newVal.clearFiles !== true && !newVal.selectedFiles) {
                    initialize($scope.rebuildInput.index + 1);
                }
            }
        });
    }
    ;
    angular.module('umbraco').controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController).run(function (mediaHelper, umbRequestHelper, assetsService) {
        if (mediaHelper && mediaHelper.registerFileResolver) {
            //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
            // they contain different data structures so if we need to query against it we need to be aware of this.
            mediaHelper.registerFileResolver('Umbraco.UploadField', function (property, entity, thumbnail) {
                if (thumbnail) {
                    if (mediaHelper.detectIfImageByExtension(property.value)) {
                        //get default big thumbnail from image processor
                        var thumbnailUrl = property.value + '?rnd=' + moment(entity.updateDate).format('YYYYMMDDHHmmss') + '&width=500&animationprocessmode=first';
                        return thumbnailUrl;
                    } else {
                        return null;
                    }
                } else {
                    return property.value;
                }
            });
        }
    });
    angular.module('umbraco')    //this controller is obsolete and should not be used anymore
                                 //it proxies everything to the system media list view which has overtaken
                                 //all the work this property editor used to perform
.controller('Umbraco.PropertyEditors.FolderBrowserController', function ($rootScope, $scope, contentTypeResource) {
        //get the system media listview
        contentTypeResource.getPropertyTypeScaffold(-96).then(function (dt) {
            $scope.fakeProperty = {
                alias: 'contents',
                config: dt.config,
                description: '',
                editor: dt.editor,
                hideLabel: true,
                id: 1,
                label: 'Contents:',
                validation: {
                    mandatory: false,
                    pattern: null
                },
                value: '',
                view: dt.view
            };
        });
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GoogleMapsController', function ($element, $rootScope, $scope, notificationsService, dialogService, assetsService, $log, $timeout) {
        assetsService.loadJs('https://www.google.com/jsapi', $scope).then(function () {
            google.load('maps', '3', {
                callback: initMap,
                other_params: 'sensor=false'
            });
        });
        function initMap() {
            //Google maps is available and all components are ready to use.
            var valueArray = $scope.model.value.split(',');
            var latLng = new google.maps.LatLng(valueArray[0], valueArray[1]);
            var mapDiv = document.getElementById($scope.model.alias + '_map');
            var mapOptions = {
                zoom: $scope.model.config.zoom,
                center: latLng,
                mapTypeId: google.maps.MapTypeId[$scope.model.config.mapType]
            };
            var geocoder = new google.maps.Geocoder();
            var map = new google.maps.Map(mapDiv, mapOptions);
            var marker = new google.maps.Marker({
                map: map,
                position: latLng,
                draggable: true
            });
            google.maps.event.addListener(map, 'click', function (event) {
                dialogService.mediaPicker({
                    callback: function (data) {
                        var image = data.selection[0].src;
                        var latLng = event.latLng;
                        var marker = new google.maps.Marker({
                            map: map,
                            icon: image,
                            position: latLng,
                            draggable: true
                        });
                        google.maps.event.addListener(marker, 'dragend', function (e) {
                            var newLat = marker.getPosition().lat();
                            var newLng = marker.getPosition().lng();
                            codeLatLng(marker.getPosition(), geocoder);
                            //set the model value
                            $scope.model.vvalue = newLat + ',' + newLng;
                        });
                    }
                });
            });
            var tabShown = function (e) {
                google.maps.event.trigger(map, 'resize');
            };
            //listen for tab changes
            if (tabsCtrl != null) {
                tabsCtrl.onTabShown(function (args) {
                    tabShown();
                });
            }
            $element.closest('.umb-panel.tabbable').on('shown', '.nav-tabs a', tabShown);
            $scope.$on('$destroy', function () {
                $element.closest('.umb-panel.tabbable').off('shown', '.nav-tabs a', tabShown);
            });
        }
        function codeLatLng(latLng, geocoder) {
            geocoder.geocode({ 'latLng': latLng }, function (results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    var location = results[0].formatted_address;
                    $rootScope.$apply(function () {
                        notificationsService.success('Peter just went to: ', location);
                    });
                }
            });
        }
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again if it has changed from the server
            initMap();
        };
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.LayoutConfigController', function ($scope) {
        $scope.currentLayout = $scope.model.currentLayout;
        $scope.columns = $scope.model.columns;
        $scope.rows = $scope.model.rows;
        $scope.scaleUp = function (section, max, overflow) {
            var add = 1;
            if (overflow !== true) {
                add = max > 1 ? 1 : max;
            }
            //var add = (max > 1) ? 1 : max;
            section.grid = section.grid + add;
        };
        $scope.scaleDown = function (section) {
            var remove = section.grid > 1 ? 1 : 0;
            section.grid = section.grid - remove;
        };
        $scope.percentage = function (spans) {
            return (spans / $scope.columns * 100).toFixed(8);
        };
        /****************
    		    Section
    		*****************/
        $scope.configureSection = function (section, template) {
            if (section === undefined) {
                var space = $scope.availableLayoutSpace > 4 ? 4 : $scope.availableLayoutSpace;
                section = { grid: space };
                template.sections.push(section);
            }
            $scope.currentSection = section;
            $scope.currentSection.allowAll = section.allowAll || !section.allowed || !section.allowed.length;
        };
        $scope.toggleAllowed = function (section) {
            if (section.allowed) {
                delete section.allowed;
            } else {
                section.allowed = [];
            }
        };
        $scope.deleteSection = function (section, template) {
            if ($scope.currentSection === section) {
                $scope.currentSection = undefined;
            }
            var index = template.sections.indexOf(section);
            template.sections.splice(index, 1);
        };
        $scope.closeSection = function () {
            $scope.currentSection = undefined;
        };
        $scope.$watch('currentLayout', function (layout) {
            if (layout) {
                var total = 0;
                _.forEach(layout.sections, function (section) {
                    total = total + section.grid;
                });
                $scope.availableLayoutSpace = $scope.columns - total;
            }
        }, true);
    });
    function RowConfigController($scope) {
        $scope.currentRow = $scope.model.currentRow;
        $scope.editors = $scope.model.editors;
        $scope.columns = $scope.model.columns;
        $scope.scaleUp = function (section, max, overflow) {
            var add = 1;
            if (overflow !== true) {
                add = max > 1 ? 1 : max;
            }
            //var add = (max > 1) ? 1 : max;
            section.grid = section.grid + add;
        };
        $scope.scaleDown = function (section) {
            var remove = section.grid > 1 ? 1 : 0;
            section.grid = section.grid - remove;
        };
        $scope.percentage = function (spans) {
            return (spans / $scope.columns * 100).toFixed(8);
        };
        /****************
        area
    *****************/
        $scope.configureCell = function (cell, row) {
            if ($scope.currentCell && $scope.currentCell === cell) {
                delete $scope.currentCell;
            } else {
                if (cell === undefined) {
                    var available = $scope.availableRowSpace;
                    var space = 4;
                    if (available < 4 && available > 0) {
                        space = available;
                    }
                    cell = { grid: space };
                    row.areas.push(cell);
                }
                $scope.currentCell = cell;
                $scope.currentCell.allowAll = cell.allowAll || !cell.allowed || !cell.allowed.length;
            }
        };
        $scope.toggleAllowed = function (cell) {
            if (cell.allowed) {
                delete cell.allowed;
            } else {
                cell.allowed = [];
            }
        };
        $scope.deleteArea = function (cell, row) {
            if ($scope.currentCell === cell) {
                $scope.currentCell = undefined;
            }
            var index = row.areas.indexOf(cell);
            row.areas.splice(index, 1);
        };
        $scope.closeArea = function () {
            $scope.currentCell = undefined;
        };
        $scope.nameChanged = false;
        var originalName = $scope.currentRow.name;
        $scope.$watch('currentRow', function (row) {
            if (row) {
                var total = 0;
                _.forEach(row.areas, function (area) {
                    total = total + area.grid;
                });
                $scope.availableRowSpace = $scope.columns - total;
                if (originalName) {
                    if (originalName != row.name) {
                        $scope.nameChanged = true;
                    } else {
                        $scope.nameChanged = false;
                    }
                }
            }
        }, true);
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditor.RowConfigController', RowConfigController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.EmbedController', function ($scope, $rootScope, $timeout) {
        $scope.setEmbed = function () {
            $scope.embedDialog = {};
            $scope.embedDialog.view = 'embed';
            $scope.embedDialog.show = true;
            $scope.embedDialog.submit = function (model) {
                $scope.control.value = model.embed.preview;
                $scope.embedDialog.show = false;
                $scope.embedDialog = null;
            };
            $scope.embedDialog.close = function (oldModel) {
                $scope.embedDialog.show = false;
                $scope.embedDialog = null;
            };
        };
        $timeout(function () {
            if ($scope.control.$initializing) {
                $scope.setEmbed();
            }
        }, 200);
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MacroController', function ($scope, $rootScope, $timeout, dialogService, macroResource, macroService, $routeParams) {
        $scope.title = 'Click to insert macro';
        $scope.setMacro = function () {
            var dialogData = {
                richTextEditor: true,
                macroData: $scope.control.value || { macroAlias: $scope.control.editor.config && $scope.control.editor.config.macroAlias ? $scope.control.editor.config.macroAlias : '' }
            };
            $scope.macroPickerOverlay = {};
            $scope.macroPickerOverlay.view = 'macropicker';
            $scope.macroPickerOverlay.dialogData = dialogData;
            $scope.macroPickerOverlay.show = true;
            $scope.macroPickerOverlay.submit = function (model) {
                var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                $scope.control.value = {
                    macroAlias: macroObject.macroAlias,
                    macroParamsDictionary: macroObject.macroParamsDictionary
                };
                $scope.setPreview($scope.control.value);
                $scope.macroPickerOverlay.show = false;
                $scope.macroPickerOverlay = null;
            };
            $scope.macroPickerOverlay.close = function (oldModel) {
                $scope.macroPickerOverlay.show = false;
                $scope.macroPickerOverlay = null;
            };
        };
        $scope.setPreview = function (macro) {
            var contentId = $routeParams.id;
            macroResource.getMacroResultAsHtmlForEditor(macro.macroAlias, contentId, macro.macroParamsDictionary).then(function (htmlResult) {
                $scope.title = macro.macroAlias;
                if (htmlResult.trim().length > 0 && htmlResult.indexOf('Macro:') < 0) {
                    $scope.preview = htmlResult;
                }
            });
        };
        $timeout(function () {
            if ($scope.control.$initializing) {
                $scope.setMacro();
            } else if ($scope.control.value) {
                $scope.setPreview($scope.control.value);
            }
        }, 200);
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.MediaController', function ($scope, $rootScope, $timeout, userService) {
        if (!$scope.model.config.startNodeId) {
            userService.getCurrentUser().then(function (userData) {
                $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
            });
        }
        $scope.setImage = function () {
            $scope.mediaPickerOverlay = {};
            $scope.mediaPickerOverlay.view = 'mediapicker';
            $scope.mediaPickerOverlay.startNodeId = $scope.model.config && $scope.model.config.startNodeId ? $scope.model.config.startNodeId : undefined;
            $scope.mediaPickerOverlay.startNodeIsVirtual = $scope.mediaPickerOverlay.startNodeId ? $scope.model.config.startNodeIsVirtual : undefined;
            $scope.mediaPickerOverlay.cropSize = $scope.control.editor.config && $scope.control.editor.config.size ? $scope.control.editor.config.size : undefined;
            $scope.mediaPickerOverlay.showDetails = true;
            $scope.mediaPickerOverlay.disableFolderSelect = true;
            $scope.mediaPickerOverlay.onlyImages = true;
            $scope.mediaPickerOverlay.show = true;
            $scope.mediaPickerOverlay.submit = function (model) {
                var selectedImage = model.selectedImages[0];
                $scope.control.value = {
                    focalPoint: selectedImage.focalPoint,
                    id: selectedImage.id,
                    udi: selectedImage.udi,
                    image: selectedImage.image,
                    altText: selectedImage.altText
                };
                $scope.setUrl();
                $scope.mediaPickerOverlay.show = false;
                $scope.mediaPickerOverlay = null;
            };
            $scope.mediaPickerOverlay.close = function (oldModel) {
                $scope.mediaPickerOverlay.show = false;
                $scope.mediaPickerOverlay = null;
            };
        };
        $scope.setUrl = function () {
            if ($scope.control.value.image) {
                var url = $scope.control.value.image;
                if ($scope.control.editor.config && $scope.control.editor.config.size) {
                    url += '?width=' + $scope.control.editor.config.size.width;
                    url += '&height=' + $scope.control.editor.config.size.height;
                    url += '&animationprocessmode=first';
                    if ($scope.control.value.focalPoint) {
                        url += '&center=' + $scope.control.value.focalPoint.top + ',' + $scope.control.value.focalPoint.left;
                        url += '&mode=crop';
                    }
                }
                // set default size if no crop present (moved from the view)
                if (url.indexOf('?') == -1) {
                    url += '?width=800&upscale=false&animationprocessmode=false';
                }
                $scope.url = url;
            }
        };
        $timeout(function () {
            if ($scope.control.$initializing) {
                $scope.setImage();
            } else if ($scope.control.value) {
                $scope.setUrl();
            }
        }, 200);
    });
    (function () {
        'use strict';
        function GridRichTextEditorController($scope, tinyMceService, macroService, editorState) {
            var vm = this;
            vm.openLinkPicker = openLinkPicker;
            vm.openMediaPicker = openMediaPicker;
            vm.openMacroPicker = openMacroPicker;
            vm.openEmbed = openEmbed;
            function openLinkPicker(editor, currentTarget, anchorElement) {
                vm.linkPickerOverlay = {
                    view: 'linkpicker',
                    currentTarget: currentTarget,
                    anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)),
                    show: true,
                    submit: function (model) {
                        tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
                        vm.linkPickerOverlay.show = false;
                        vm.linkPickerOverlay = null;
                    }
                };
            }
            function openMediaPicker(editor, currentTarget, userData) {
                vm.mediaPickerOverlay = {
                    currentTarget: currentTarget,
                    onlyImages: true,
                    showDetails: true,
                    startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
                    view: 'mediapicker',
                    show: true,
                    submit: function (model) {
                        tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]);
                        vm.mediaPickerOverlay.show = false;
                        vm.mediaPickerOverlay = null;
                    }
                };
            }
            function openEmbed(editor) {
                vm.embedOverlay = {
                    view: 'embed',
                    show: true,
                    submit: function (model) {
                        tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview);
                        vm.embedOverlay.show = false;
                        vm.embedOverlay = null;
                    }
                };
            }
            function openMacroPicker(editor, dialogData) {
                vm.macroPickerOverlay = {
                    view: 'macropicker',
                    dialogData: dialogData,
                    show: true,
                    submit: function (model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                        tinyMceService.insertMacroInEditor(editor, macroObject, $scope);
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                    }
                };
            }
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.RichTextEditorController', GridRichTextEditorController);
    }());
    angular.module('umbraco').controller('Umbraco.PropertyEditors.Grid.TextStringController', function ($scope, $rootScope, $timeout, dialogService) {
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridController', function ($scope, $http, assetsService, localizationService, $rootScope, dialogService, gridService, mediaResource, imageHelper, $timeout, umbRequestHelper, angularHelper, $element, eventsService) {
        // Grid status variables
        var placeHolder = '';
        var currentForm = angularHelper.getCurrentForm($scope);
        $scope.currentRow = null;
        $scope.currentCell = null;
        $scope.currentToolsControl = null;
        $scope.currentControl = null;
        $scope.openRTEToolbarId = null;
        $scope.hasSettings = false;
        $scope.showRowConfigurations = true;
        $scope.sortMode = false;
        $scope.reorderKey = 'general_reorder';
        // *********************************************
        // Sortable options
        // *********************************************
        var draggedRteSettings;
        $scope.sortableOptionsRow = {
            distance: 10,
            cursor: 'move',
            placeholder: 'ui-sortable-placeholder',
            handle: '.umb-row-title-bar',
            helper: 'clone',
            forcePlaceholderSize: true,
            tolerance: 'pointer',
            zIndex: 1000000000000000000,
            scrollSensitivity: 100,
            cursorAt: {
                top: 40,
                left: 60
            },
            sort: function (event, ui) {
                /* prevent vertical scroll out of the screen */
                var max = $('.umb-grid').width() - 150;
                if (parseInt(ui.helper.css('left')) > max) {
                    ui.helper.css({ 'left': max + 'px' });
                }
                if (parseInt(ui.helper.css('left')) < 20) {
                    ui.helper.css({ 'left': 20 });
                }
            },
            start: function (e, ui) {
                // Fade out row when sorting
                ui.item.context.style.display = 'block';
                ui.item.context.style.opacity = '0.5';
                draggedRteSettings = {};
                ui.item.find('.mceNoEditor').each(function () {
                    // remove all RTEs in the dragged row and save their settings
                    var id = $(this).attr('id');
                    draggedRteSettings[id] = _.findWhere(tinyMCE.editors, { id: id }).settings;    // tinyMCE.execCommand("mceRemoveEditor", false, id);
                });
            },
            stop: function (e, ui) {
                // Fade in row when sorting stops
                ui.item.context.style.opacity = '1';
                // reset all RTEs affected by the dragging
                ui.item.parents('.umb-column').find('.mceNoEditor').each(function () {
                    var id = $(this).attr('id');
                    draggedRteSettings[id] = draggedRteSettings[id] || _.findWhere(tinyMCE.editors, { id: id }).settings;
                    tinyMCE.execCommand('mceRemoveEditor', false, id);
                    tinyMCE.init(draggedRteSettings[id]);
                });
                currentForm.$setDirty();
            }
        };
        var notIncludedRte = [];
        var cancelMove = false;
        var startingArea;
        $scope.sortableOptionsCell = {
            distance: 10,
            cursor: 'move',
            placeholder: 'ui-sortable-placeholder',
            handle: '.umb-control-handle',
            helper: 'clone',
            connectWith: '.umb-cell-inner',
            forcePlaceholderSize: true,
            tolerance: 'pointer',
            zIndex: 1000000000000000000,
            scrollSensitivity: 100,
            cursorAt: {
                top: 45,
                left: 90
            },
            sort: function (event, ui) {
                /* prevent vertical scroll out of the screen */
                var position = parseInt(ui.item.parent().offset().left) + parseInt(ui.helper.css('left')) - parseInt($('.umb-grid').offset().left);
                var max = $('.umb-grid').width() - 220;
                if (position > max) {
                    ui.helper.css({ 'left': max - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' });
                }
                if (position < 0) {
                    ui.helper.css({ 'left': 0 - parseInt(ui.item.parent().offset().left) + parseInt($('.umb-grid').offset().left) + 'px' });
                }
            },
            over: function (event, ui) {
                var area = $(event.target).scope().area;
                var allowedEditors = area.allowed;
                if ($.inArray(ui.item.scope().control.editor.alias, allowedEditors) < 0 && allowedEditors || startingArea != area && area.maxItems != '' && area.maxItems > 0 && area.maxItems < area.controls.length + 1) {
                    $scope.$apply(function () {
                        $(event.target).scope().area.dropNotAllowed = true;
                    });
                    ui.placeholder.hide();
                    cancelMove = true;
                } else {
                    if ($(event.target).scope().area.controls.length == 0) {
                        $scope.$apply(function () {
                            $(event.target).scope().area.dropOnEmpty = true;
                        });
                        ui.placeholder.hide();
                    } else {
                        ui.placeholder.show();
                    }
                    cancelMove = false;
                }
            },
            out: function (event, ui) {
                $scope.$apply(function () {
                    $(event.target).scope().area.dropNotAllowed = false;
                    $(event.target).scope().area.dropOnEmpty = false;
                });
            },
            update: function (event, ui) {
                /* add all RTEs which are affected by the dragging */
                if (!ui.sender) {
                    if (cancelMove) {
                        ui.item.sortable.cancel();
                    }
                    ui.item.parents('.umb-cell.content').find('.mceNoEditor').each(function () {
                        if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                            notIncludedRte.splice(0, 0, $(this).attr('id'));
                        }
                    });
                } else {
                    $(event.target).find('.mceNoEditor').each(function () {
                        if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                            notIncludedRte.splice(0, 0, $(this).attr('id'));
                        }
                    });
                }
                currentForm.$setDirty();
            },
            start: function (e, ui) {
                //Get the starting area for reference
                var area = $(e.target).scope().area;
                startingArea = area;
                // fade out control when sorting
                ui.item.context.style.display = 'block';
                ui.item.context.style.opacity = '0.5';
                // reset dragged RTE settings in case a RTE isn't dragged
                draggedRteSettings = undefined;
                ui.item.context.style.display = 'block';
                ui.item.find('.mceNoEditor').each(function () {
                    notIncludedRte = [];
                    var editors = _.findWhere(tinyMCE.editors, { id: $(this).attr('id') });
                    // save the dragged RTE settings
                    if (editors) {
                        draggedRteSettings = editors.settings;
                        // remove the dragged RTE
                        tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                    }
                });
            },
            stop: function (e, ui) {
                // Fade in control when sorting stops
                ui.item.context.style.opacity = '1';
                ui.item.offsetParent().find('.mceNoEditor').each(function () {
                    if ($.inArray($(this).attr('id'), notIncludedRte) < 0) {
                        // add all dragged's neighbouring RTEs in the new cell
                        notIncludedRte.splice(0, 0, $(this).attr('id'));
                    }
                });
                $timeout(function () {
                    // reconstruct the dragged RTE (could be undefined when dragging something else than RTE)
                    if (draggedRteSettings !== undefined) {
                        tinyMCE.init(draggedRteSettings);
                    }
                    _.forEach(notIncludedRte, function (id) {
                        // reset all the other RTEs
                        if (draggedRteSettings === undefined || id !== draggedRteSettings.id) {
                            var rteSettings = _.findWhere(tinyMCE.editors, { id: id }).settings;
                            tinyMCE.execCommand('mceRemoveEditor', false, id);
                            tinyMCE.init(rteSettings);
                        }
                    });
                }, 500, false);
                $scope.$apply(function () {
                    var cell = $(e.target).scope().area;
                    cell.hasActiveChild = hasActiveChild(cell, cell.controls);
                    cell.active = false;
                });
            }
        };
        $scope.toggleSortMode = function () {
            $scope.sortMode = !$scope.sortMode;
            if ($scope.sortMode) {
                $scope.reorderKey = 'general_reorderDone';
            } else {
                $scope.reorderKey = 'general_reorder';
            }
        };
        $scope.showReorderButton = function () {
            if ($scope.model.value && $scope.model.value.sections) {
                for (var i = 0; $scope.model.value.sections.length > i; i++) {
                    var section = $scope.model.value.sections[i];
                    if (section.rows && section.rows.length > 0) {
                        return true;
                    }
                }
            }
        };
        // *********************************************
        // Add items overlay menu
        // *********************************************
        $scope.openEditorOverlay = function (event, area, index, key) {
            $scope.editorOverlay = {
                view: 'itempicker',
                filter: area.$allowedEditors.length > 15,
                title: localizationService.localize('grid_insertControl'),
                availableItems: area.$allowedEditors,
                event: event,
                show: true,
                submit: function (model) {
                    if (model.selectedItem) {
                        $scope.addControl(model.selectedItem, area, index);
                        $scope.editorOverlay.show = false;
                        $scope.editorOverlay = null;
                    }
                }
            };
        };
        // *********************************************
        // Template management functions
        // *********************************************
        $scope.addTemplate = function (template) {
            $scope.model.value = angular.copy(template);
            //default row data
            _.forEach($scope.model.value.sections, function (section) {
                $scope.initSection(section);
            });
        };
        // *********************************************
        // Row management function
        // *********************************************
        $scope.clickRow = function (index, rows) {
            rows[index].active = true;
        };
        $scope.clickOutsideRow = function (index, rows) {
            rows[index].active = false;
        };
        function getAllowedLayouts(section) {
            var layouts = $scope.model.config.items.layouts;
            //This will occur if it is a new section which has been
            // created from a 'template'
            if (section.allowed && section.allowed.length > 0) {
                return _.filter(layouts, function (layout) {
                    return _.indexOf(section.allowed, layout.name) >= 0;
                });
            } else {
                return layouts;
            }
        }
        $scope.addRow = function (section, layout, isInit) {
            //copy the selected layout into the rows collection
            var row = angular.copy(layout);
            // Init row value
            row = $scope.initRow(row);
            // Push the new row
            if (row) {
                section.rows.push(row);
            }
            if (!isInit) {
                currentForm.$setDirty();
            }
            $scope.showRowConfigurations = false;
            eventsService.emit('grid.rowAdded', {
                scope: $scope,
                element: $element,
                row: row
            });
        };
        $scope.removeRow = function (section, $index) {
            if (section.rows.length > 0) {
                section.rows.splice($index, 1);
                $scope.currentRow = null;
                $scope.openRTEToolbarId = null;
                currentForm.$setDirty();
            }
            if (section.rows.length === 0) {
                $scope.showRowConfigurations = true;
            }
        };
        var shouldApply = function (item, itemType, gridItem) {
            if (item.applyTo === undefined || item.applyTo === null || item.applyTo === '') {
                return true;
            }
            if (typeof item.applyTo === 'string') {
                return item.applyTo === itemType;
            }
            if (itemType === 'row') {
                if (item.applyTo.row === undefined) {
                    return false;
                }
                if (item.applyTo.row === null || item.applyTo.row === '') {
                    return true;
                }
                var rows = item.applyTo.row.split(',');
                return _.indexOf(rows, gridItem.name) !== -1;
            } else if (itemType === 'cell') {
                if (item.applyTo.cell === undefined) {
                    return false;
                }
                if (item.applyTo.cell === null || item.applyTo.cell === '') {
                    return true;
                }
                var cells = item.applyTo.cell.split(',');
                var cellSize = gridItem.grid.toString();
                return _.indexOf(cells, cellSize) !== -1;
            }
        };
        $scope.editGridItemSettings = function (gridItem, itemType) {
            placeHolder = '{0}';
            var styles, config;
            if (itemType === 'control') {
                styles = null;
                config = angular.copy(gridItem.editor.config.settings);
            } else {
                styles = _.filter(angular.copy($scope.model.config.items.styles), function (item) {
                    return shouldApply(item, itemType, gridItem);
                });
                config = _.filter(angular.copy($scope.model.config.items.config), function (item) {
                    return shouldApply(item, itemType, gridItem);
                });
            }
            if (angular.isObject(gridItem.config)) {
                _.each(config, function (cfg) {
                    var val = gridItem.config[cfg.key];
                    if (val) {
                        cfg.value = stripModifier(val, cfg.modifier);
                    }
                });
            }
            if (angular.isObject(gridItem.styles)) {
                _.each(styles, function (style) {
                    var val = gridItem.styles[style.key];
                    if (val) {
                        style.value = stripModifier(val, style.modifier);
                    }
                });
            }
            $scope.gridItemSettingsDialog = {};
            $scope.gridItemSettingsDialog.view = 'views/propertyeditors/grid/dialogs/config.html';
            $scope.gridItemSettingsDialog.title = 'Settings';
            $scope.gridItemSettingsDialog.styles = styles;
            $scope.gridItemSettingsDialog.config = config;
            $scope.gridItemSettingsDialog.show = true;
            $scope.gridItemSettingsDialog.submit = function (model) {
                var styleObject = {};
                var configObject = {};
                _.each(model.styles, function (style) {
                    if (style.value) {
                        styleObject[style.key] = addModifier(style.value, style.modifier);
                    }
                });
                _.each(model.config, function (cfg) {
                    if (cfg.value) {
                        configObject[cfg.key] = addModifier(cfg.value, cfg.modifier);
                    }
                });
                gridItem.styles = styleObject;
                gridItem.config = configObject;
                gridItem.hasConfig = gridItemHasConfig(styleObject, configObject);
                currentForm.$setDirty();
                $scope.gridItemSettingsDialog.show = false;
                $scope.gridItemSettingsDialog = null;
            };
            $scope.gridItemSettingsDialog.close = function (oldModel) {
                $scope.gridItemSettingsDialog.show = false;
                $scope.gridItemSettingsDialog = null;
            };
        };
        function stripModifier(val, modifier) {
            if (!val || !modifier || modifier.indexOf(placeHolder) < 0) {
                return val;
            } else {
                var paddArray = modifier.split(placeHolder);
                if (paddArray.length == 1) {
                    if (modifier.indexOf(placeHolder) === 0) {
                        return val.slice(0, -paddArray[0].length);
                    } else {
                        return val.slice(paddArray[0].length, 0);
                    }
                } else {
                    if (paddArray[1].length === 0) {
                        return val.slice(paddArray[0].length);
                    }
                    return val.slice(paddArray[0].length, -paddArray[1].length);
                }
            }
        }
        var addModifier = function (val, modifier) {
            if (!modifier || modifier.indexOf(placeHolder) < 0) {
                return val;
            } else {
                return modifier.replace(placeHolder, val);
            }
        };
        function gridItemHasConfig(styles, config) {
            if (_.isEmpty(styles) && _.isEmpty(config)) {
                return false;
            } else {
                return true;
            }
        }
        // *********************************************
        // Area management functions
        // *********************************************
        $scope.clickCell = function (index, cells, row) {
            cells[index].active = true;
            row.hasActiveChild = true;
        };
        $scope.clickOutsideCell = function (index, cells, row) {
            cells[index].active = false;
            row.hasActiveChild = hasActiveChild(row, cells);
        };
        $scope.cellPreview = function (cell) {
            if (cell && cell.$allowedEditors) {
                var editor = cell.$allowedEditors[0];
                return editor.icon;
            } else {
                return 'icon-layout';
            }
        };
        // *********************************************
        // Control management functions
        // *********************************************
        $scope.clickControl = function (index, controls, cell) {
            controls[index].active = true;
            cell.hasActiveChild = true;
        };
        $scope.clickOutsideControl = function (index, controls, cell) {
            controls[index].active = false;
            cell.hasActiveChild = hasActiveChild(cell, controls);
        };
        function hasActiveChild(item, children) {
            var activeChild = false;
            for (var i = 0; children.length > i; i++) {
                var child = children[i];
                if (child.active) {
                    activeChild = true;
                }
            }
            if (activeChild) {
                return true;
            }
        }
        var guid = function () {
            function s4() {
                return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
            }
            return function () {
                return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
            };
        }();
        $scope.setUniqueId = function (cell, index) {
            return guid();
        };
        $scope.addControl = function (editor, cell, index, initialize) {
            initialize = initialize !== false;
            var newControl = {
                value: null,
                editor: editor,
                $initializing: initialize
            };
            if (index === undefined) {
                index = cell.controls.length;
            }
            newControl.active = true;
            //populate control
            $scope.initControl(newControl, index + 1);
            cell.controls.push(newControl);
            eventsService.emit('grid.itemAdded', {
                scope: $scope,
                element: $element,
                cell: cell,
                item: newControl
            });
        };
        $scope.addTinyMce = function (cell) {
            var rte = $scope.getEditor('rte');
            $scope.addControl(rte, cell);
        };
        $scope.getEditor = function (alias) {
            return _.find($scope.availableEditors, function (editor) {
                return editor.alias === alias;
            });
        };
        $scope.removeControl = function (cell, $index) {
            $scope.currentControl = null;
            cell.controls.splice($index, 1);
        };
        $scope.percentage = function (spans) {
            return (spans / $scope.model.config.items.columns * 100).toFixed(8);
        };
        $scope.clearPrompt = function (scopedObject, e) {
            scopedObject.deletePrompt = false;
            e.preventDefault();
            e.stopPropagation();
        };
        $scope.togglePrompt = function (scopedObject) {
            scopedObject.deletePrompt = !scopedObject.deletePrompt;
        };
        $scope.hidePrompt = function (scopedObject) {
            scopedObject.deletePrompt = false;
        };
        $scope.toggleAddRow = function () {
            $scope.showRowConfigurations = !$scope.showRowConfigurations;
        };
        // *********************************************
        // Initialization
        // these methods are called from ng-init on the template
        // so we can controll their first load data
        //
        // intialization sets non-saved data like percentage sizing, allowed editors and
        // other data that should all be pre-fixed with $ to strip it out on save
        // *********************************************
        // *********************************************
        // Init template + sections
        // *********************************************
        $scope.initContent = function () {
            var clear = true;
            //settings indicator shortcut
            if ($scope.model.config.items.config && $scope.model.config.items.config.length > 0 || $scope.model.config.items.styles && $scope.model.config.items.styles.length > 0) {
                $scope.hasSettings = true;
            }
            //ensure the grid has a column value set,
            //if nothing is found, set it to 12
            if (!$scope.model.config.items.columns) {
                $scope.model.config.items.columns = 12;
            } else if (angular.isString($scope.model.config.items.columns)) {
                $scope.model.config.items.columns = parseInt($scope.model.config.items.columns);
            }
            if ($scope.model.value && $scope.model.value.sections && $scope.model.value.sections.length > 0 && $scope.model.value.sections[0].rows && $scope.model.value.sections[0].rows.length > 0) {
                if ($scope.model.value.name && angular.isArray($scope.model.config.items.templates)) {
                    //This will occur if it is an existing value, in which case
                    // we need to determine which layout was applied by looking up
                    // the name
                    // TODO: We need to change this to an immutable ID!!
                    var found = _.find($scope.model.config.items.templates, function (t) {
                        return t.name === $scope.model.value.name;
                    });
                    if (found && angular.isArray(found.sections) && found.sections.length === $scope.model.value.sections.length) {
                        //Cool, we've found the template associated with our current value with matching sections counts, now we need to
                        // merge this template data on to our current value (as if it was new) so that we can preserve what is and isn't
                        // allowed for this template based on the current config.
                        _.each(found.sections, function (templateSection, index) {
                            angular.extend($scope.model.value.sections[index], angular.copy(templateSection));
                        });
                    }
                }
                _.forEach($scope.model.value.sections, function (section, index) {
                    if (section.grid > 0) {
                        $scope.initSection(section);
                        //we do this to ensure that the grid can be reset by deleting the last row
                        if (section.rows.length > 0) {
                            clear = false;
                        }
                    } else {
                        $scope.model.value.sections.splice(index, 1);
                    }
                });
            } else if ($scope.model.config.items.templates && $scope.model.config.items.templates.length === 1) {
                $scope.addTemplate($scope.model.config.items.templates[0]);
                clear = false;
            }
            if (clear) {
                $scope.model.value = undefined;
            }
        };
        $scope.initSection = function (section) {
            section.$percentage = $scope.percentage(section.grid);
            section.$allowedLayouts = getAllowedLayouts(section);
            if (!section.rows || section.rows.length === 0) {
                section.rows = [];
                if (section.$allowedLayouts.length === 1) {
                    $scope.addRow(section, section.$allowedLayouts[0], true);
                }
            } else {
                _.forEach(section.rows, function (row, index) {
                    if (!row.$initialized) {
                        var initd = $scope.initRow(row);
                        //if init fails, remove
                        if (!initd) {
                            section.rows.splice(index, 1);
                        } else {
                            section.rows[index] = initd;
                        }
                    }
                });
                // if there is more than one row added - hide row add tools
                $scope.showRowConfigurations = false;
            }
        };
        // *********************************************
        // Init layout / row
        // *********************************************
        $scope.initRow = function (row) {
            //merge the layout data with the original config data
            //if there are no config info on this, splice it out
            var original = _.find($scope.model.config.items.layouts, function (o) {
                return o.name === row.name;
            });
            if (!original) {
                return null;
            } else {
                //make a copy to not touch the original config
                original = angular.copy(original);
                original.styles = row.styles;
                original.config = row.config;
                original.hasConfig = gridItemHasConfig(row.styles, row.config);
                //sync area configuration
                _.each(original.areas, function (area, areaIndex) {
                    if (area.grid > 0) {
                        var currentArea = row.areas[areaIndex];
                        if (currentArea) {
                            area.config = currentArea.config;
                            area.styles = currentArea.styles;
                            area.hasConfig = gridItemHasConfig(currentArea.styles, currentArea.config);
                        }
                        //set editor permissions
                        if (!area.allowed || area.allowAll === true) {
                            area.$allowedEditors = $scope.availableEditors;
                            area.$allowsRTE = true;
                        } else {
                            area.$allowedEditors = _.filter($scope.availableEditors, function (editor) {
                                return _.indexOf(area.allowed, editor.alias) >= 0;
                            });
                            if (_.indexOf(area.allowed, 'rte') >= 0) {
                                area.$allowsRTE = true;
                            }
                        }
                        //copy over existing controls into the new areas
                        if (row.areas.length > areaIndex && row.areas[areaIndex].controls) {
                            area.controls = currentArea.controls;
                            _.forEach(area.controls, function (control, controlIndex) {
                                $scope.initControl(control, controlIndex);
                            });
                        } else {
                            //if empty
                            area.controls = [];
                            //if only one allowed editor
                            if (area.$allowedEditors.length === 1) {
                                $scope.addControl(area.$allowedEditors[0], area, 0, false);
                            }
                        }
                        //set width
                        area.$percentage = $scope.percentage(area.grid);
                        area.$uniqueId = $scope.setUniqueId();
                    } else {
                        original.areas.splice(areaIndex, 1);
                    }
                });
                //replace the old row
                original.$initialized = true;
                //set a disposable unique ID
                original.$uniqueId = $scope.setUniqueId();
                //set a no disposable unique ID (util for row styling)
                original.id = !row.id ? $scope.setUniqueId() : row.id;
                return original;
            }
        };
        // *********************************************
        // Init control
        // *********************************************
        $scope.initControl = function (control, index) {
            control.$index = index;
            control.$uniqueId = $scope.setUniqueId();
            //error handling in case of missing editor..
            //should only happen if stripped earlier
            if (!control.editor) {
                control.$editorPath = 'views/propertyeditors/grid/editors/error.html';
            }
            if (!control.$editorPath) {
                var editorConfig = $scope.getEditor(control.editor.alias);
                if (editorConfig) {
                    control.editor = editorConfig;
                    //if its an absolute path
                    if (control.editor.view.startsWith('/') || control.editor.view.startsWith('~/')) {
                        control.$editorPath = umbRequestHelper.convertVirtualToAbsolutePath(control.editor.view);
                    } else {
                        //use convention
                        control.$editorPath = 'views/propertyeditors/grid/editors/' + control.editor.view + '.html';
                    }
                } else {
                    control.$editorPath = 'views/propertyeditors/grid/editors/error.html';
                }
            }
        };
        gridService.getGridEditors().then(function (response) {
            $scope.availableEditors = response.data;
            //Localize the grid editor names
            angular.forEach($scope.availableEditors, function (value, key) {
                //If no translation is provided, keep using the editor name from the manifest
                if (localizationService.dictionary.hasOwnProperty('grid_' + value.alias)) {
                    value.name = localizationService.localize('grid_' + value.alias);
                }
            });
            $scope.contentReady = true;
            // *********************************************
            // Init grid
            // *********************************************
            eventsService.emit('grid.initializing', {
                scope: $scope,
                element: $element
            });
            $scope.initContent();
            eventsService.emit('grid.initialized', {
                scope: $scope,
                element: $element
            });
        });
        //Clean the grid value before submitting to the server, we don't need
        // all of that grid configuration in the value to be stored!! All of that
        // needs to be merged in at runtime to ensure that the real config values are used
        // if they are ever updated.
        var unsubscribe = $scope.$on('formSubmitting', function () {
            if ($scope.model.value && $scope.model.value.sections) {
                _.each($scope.model.value.sections, function (section) {
                    if (section.rows) {
                        _.each(section.rows, function (row) {
                            if (row.areas) {
                                _.each(row.areas, function (area) {
                                    //Remove the 'editors' - these are the allowed editors, these will
                                    // be injected at runtime to this editor, it should not be persisted
                                    if (area.editors) {
                                        delete area.editors;
                                    }
                                    if (area.controls) {
                                        _.each(area.controls, function (control) {
                                            if (control.editor) {
                                                //replace
                                                var alias = control.editor.alias;
                                                control.editor = { alias: alias };
                                            }
                                        });
                                    }
                                });
                            }
                        });
                    }
                });
            }
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.GridPrevalueEditorController', function ($scope, $http, assetsService, $rootScope, dialogService, mediaResource, gridService, imageHelper, $timeout) {
        var emptyModel = {
            styles: [{
                    label: 'Set a background image',
                    description: 'Set a row background',
                    key: 'background-image',
                    view: 'imagepicker',
                    modifier: 'url({0})'
                }],
            config: [{
                    label: 'Class',
                    description: 'Set a css class',
                    key: 'class',
                    view: 'textstring'
                }],
            columns: 12,
            templates: [
                {
                    name: '1 column layout',
                    sections: [{ grid: 12 }]
                },
                {
                    name: '2 column layout',
                    sections: [
                        { grid: 4 },
                        { grid: 8 }
                    ]
                }
            ],
            layouts: [
                {
                    label: 'Headline',
                    name: 'Headline',
                    areas: [{
                            grid: 12,
                            editors: ['headline']
                        }]
                },
                {
                    label: 'Article',
                    name: 'Article',
                    areas: [
                        { grid: 4 },
                        { grid: 8 }
                    ]
                }
            ]
        };
        /****************
            template
        *****************/
        $scope.configureTemplate = function (template) {
            var templatesCopy = angular.copy($scope.model.value.templates);
            if (template === undefined) {
                template = {
                    name: '',
                    sections: []
                };
                $scope.model.value.templates.push(template);
            }
            $scope.layoutConfigOverlay = {};
            $scope.layoutConfigOverlay.view = 'views/propertyEditors/grid/dialogs/layoutconfig.html';
            $scope.layoutConfigOverlay.currentLayout = template;
            $scope.layoutConfigOverlay.rows = $scope.model.value.layouts;
            $scope.layoutConfigOverlay.columns = $scope.model.value.columns;
            $scope.layoutConfigOverlay.show = true;
            $scope.layoutConfigOverlay.submit = function (model) {
                $scope.layoutConfigOverlay.show = false;
                $scope.layoutConfigOverlay = null;
            };
            $scope.layoutConfigOverlay.close = function (oldModel) {
                //reset templates
                $scope.model.value.templates = templatesCopy;
                $scope.layoutConfigOverlay.show = false;
                $scope.layoutConfigOverlay = null;
            };
        };
        $scope.deleteTemplate = function (index) {
            $scope.model.value.templates.splice(index, 1);
        };
        /****************
            Row
        *****************/
        $scope.configureLayout = function (layout) {
            var layoutsCopy = angular.copy($scope.model.value.layouts);
            if (layout === undefined) {
                layout = {
                    name: '',
                    areas: []
                };
                $scope.model.value.layouts.push(layout);
            }
            $scope.rowConfigOverlay = {};
            $scope.rowConfigOverlay.view = 'views/propertyEditors/grid/dialogs/rowconfig.html';
            $scope.rowConfigOverlay.currentRow = layout;
            $scope.rowConfigOverlay.editors = $scope.editors;
            $scope.rowConfigOverlay.columns = $scope.model.value.columns;
            $scope.rowConfigOverlay.show = true;
            $scope.rowConfigOverlay.submit = function (model) {
                $scope.rowConfigOverlay.show = false;
                $scope.rowConfigOverlay = null;
            };
            $scope.rowConfigOverlay.close = function (oldModel) {
                $scope.model.value.layouts = layoutsCopy;
                $scope.rowConfigOverlay.show = false;
                $scope.rowConfigOverlay = null;
            };
        };
        //var rowDeletesPending = false;
        $scope.deleteLayout = function (index) {
            $scope.rowDeleteOverlay = {};
            $scope.rowDeleteOverlay.view = 'views/propertyEditors/grid/dialogs/rowdeleteconfirm.html';
            $scope.rowDeleteOverlay.dialogData = { rowName: $scope.model.value.layouts[index].name };
            $scope.rowDeleteOverlay.show = true;
            $scope.rowDeleteOverlay.submit = function (model) {
                $scope.model.value.layouts.splice(index, 1);
                $scope.rowDeleteOverlay.show = false;
                $scope.rowDeleteOverlay = null;
            };
            $scope.rowDeleteOverlay.close = function (oldModel) {
                $scope.rowDeleteOverlay.show = false;
                $scope.rowDeleteOverlay = null;
            };
        };
        /****************
            utillities
        *****************/
        $scope.toggleCollection = function (collection, toggle) {
            if (toggle) {
                collection = [];
            } else {
                delete collection;
            }
        };
        $scope.percentage = function (spans) {
            return (spans / $scope.model.value.columns * 100).toFixed(8);
        };
        $scope.zeroWidthFilter = function (cell) {
            return cell.grid > 0;
        };
        /****************
            Config
        *****************/
        $scope.removeConfigValue = function (collection, index) {
            collection.splice(index, 1);
        };
        var editConfigCollection = function (configValues, title, callback) {
            $scope.editConfigCollectionOverlay = {};
            $scope.editConfigCollectionOverlay.view = 'views/propertyeditors/grid/dialogs/editconfig.html';
            $scope.editConfigCollectionOverlay.config = configValues;
            $scope.editConfigCollectionOverlay.title = title;
            $scope.editConfigCollectionOverlay.show = true;
            $scope.editConfigCollectionOverlay.submit = function (model) {
                callback(model.config);
                $scope.editConfigCollectionOverlay.show = false;
                $scope.editConfigCollectionOverlay = null;
            };
            $scope.editConfigCollectionOverlay.close = function (oldModel) {
                $scope.editConfigCollectionOverlay.show = false;
                $scope.editConfigCollectionOverlay = null;
            };
        };
        $scope.editConfig = function () {
            editConfigCollection($scope.model.value.config, 'Settings', function (data) {
                $scope.model.value.config = data;
            });
        };
        $scope.editStyles = function () {
            editConfigCollection($scope.model.value.styles, 'Styling', function (data) {
                $scope.model.value.styles = data;
            });
        };
        /****************
            editors
        *****************/
        gridService.getGridEditors().then(function (response) {
            $scope.editors = response.data;
        });
        /* init grid data */
        if (!$scope.model.value || $scope.model.value === '' || !$scope.model.value.templates) {
            $scope.model.value = emptyModel;
        } else {
            if (!$scope.model.value.columns) {
                $scope.model.value.columns = emptyModel.columns;
            }
            if (!$scope.model.value.config) {
                $scope.model.value.config = [];
            }
            if (!$scope.model.value.styles) {
                $scope.model.value.styles = [];
            }
        }
        /****************
            Clean up
        *****************/
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var ts = $scope.model.value.templates;
            var ls = $scope.model.value.layouts;
            _.each(ts, function (t) {
                _.each(t.sections, function (section, index) {
                    if (section.grid === 0) {
                        t.sections.splice(index, 1);
                    }
                });
            });
            _.each(ls, function (l) {
                _.each(l.areas, function (area, index) {
                    if (area.grid === 0) {
                        l.areas.splice(index, 1);
                    }
                });
            });
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.IdWithGuidValueController
 * @function
 * 
 * @description
 * The controller for the idwithguid property editor, which formats the ID as normal
 * with the GUID in smaller text below, as used across the backoffice.
*/
    function IdWithGuidValueController($rootScope, $scope, $filter) {
        function formatDisplayValue() {
            if ($scope.model.value.length > 1) {
                $scope.displayid = $scope.model.value[0];
                $scope.displayguid = $scope.model.value[1];
            } else {
                $scope.displayid = $scope.model.value;
            }
        }
        //format the display value on init:
        formatDisplayValue();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.IdWithGuidValueController', IdWithGuidValueController);
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ImageCropperController', function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) {
        var config = angular.copy($scope.model.config);
        $scope.imageIsLoaded = false;
        //move previously saved value to the editor
        if ($scope.model.value) {
            //backwards compat with the old file upload (incase some-one swaps them..)
            if (angular.isString($scope.model.value)) {
                config.src = $scope.model.value;
                $scope.model.value = config;
            } else if ($scope.model.value.crops) {
                //sync any config changes with the editor and drop outdated crops
                _.each($scope.model.value.crops, function (saved) {
                    var configured = _.find(config.crops, function (item) {
                        return item.alias === saved.alias;
                    });
                    if (configured && configured.height === saved.height && configured.width === saved.width) {
                        configured.coordinates = saved.coordinates;
                    }
                });
                $scope.model.value.crops = config.crops;
                //restore focalpoint if missing
                if (!$scope.model.value.focalPoint) {
                    $scope.model.value.focalPoint = {
                        left: 0.5,
                        top: 0.5
                    };
                }
            }
            $scope.imageSrc = $scope.model.value.src;
        }
        //crop a specific crop
        $scope.crop = function (crop) {
            // clone the crop so we can discard the changes
            $scope.currentCrop = angular.copy(crop);
            $scope.currentPoint = undefined;
        };
        //done cropping
        $scope.done = function () {
            if (!$scope.currentCrop) {
                return;
            }
            // find the original crop by crop alias and update its coordinates
            var editedCrop = _.find($scope.model.value.crops, function (crop) {
                return crop.alias === $scope.currentCrop.alias;
            });
            editedCrop.coordinates = $scope.currentCrop.coordinates;
            $scope.close();
            angularHelper.getCurrentForm($scope).$setDirty();
        };
        //reset the current crop
        $scope.reset = function () {
            $scope.currentCrop.coordinates = undefined;
            $scope.done();
        };
        //close crop overlay
        $scope.close = function (crop) {
            $scope.currentCrop = undefined;
            $scope.currentPoint = undefined;
        };
        //crop a specific crop
        $scope.clear = function (crop) {
            //clear current uploaded files
            fileManager.setFiles($scope.model.alias, []);
            //clear the ui
            $scope.imageSrc = undefined;
            if ($scope.model.value) {
                delete $scope.model.value;
            }
            // set form to dirty to tricker discard changes dialog
            var currForm = angularHelper.getCurrentForm($scope);
            currForm.$setDirty();
        };
        //show previews
        $scope.togglePreviews = function () {
            if ($scope.showPreviews) {
                $scope.showPreviews = false;
                $scope.tempShowPreviews = false;
            } else {
                $scope.showPreviews = true;
            }
        };
        $scope.imageLoaded = function (isCroppable, hasDimensions) {
            $scope.imageIsLoaded = true;
            $scope.isCroppable = isCroppable;
            $scope.hasDimensions = hasDimensions;
        };
        $scope.focalPointChanged = function () {
            angularHelper.getCurrentForm($scope).$setDirty();
        };
        //on image selected, update the cropper
        $scope.$on('filesSelected', function (ev, args) {
            $scope.model.value = config;
            if (args.files && args.files[0]) {
                fileManager.setFiles($scope.model.alias, args.files);
                var reader = new FileReader();
                reader.onload = function (e) {
                    $scope.$apply(function () {
                        $scope.imageSrc = e.target.result;
                    });
                };
                reader.readAsDataURL(args.files[0]);
            }
        });
        //here we declare a special method which will be called whenever the value has changed from the server
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //clear current uploaded files
            fileManager.setFiles($scope.model.alias, []);
        };
        var unsubscribe = $scope.$on('formSubmitting', function () {
            $scope.done();
        });
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }).run(function (mediaHelper, umbRequestHelper) {
        if (mediaHelper && mediaHelper.registerFileResolver) {
            //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource
            // they contain different data structures so if we need to query against it we need to be aware of this.
            mediaHelper.registerFileResolver('Umbraco.ImageCropper', function (property, entity, thumbnail) {
                if (property.value && property.value.src) {
                    if (thumbnail === true) {
                        return property.value.src + '?width=500&mode=max&animationprocessmode=first';
                    } else {
                        return property.value.src;
                    }    //this is a fallback in case the cropper has been asssigned a upload field
                } else if (angular.isString(property.value)) {
                    if (thumbnail) {
                        if (mediaHelper.detectIfImageByExtension(property.value)) {
                            var thumbnailUrl = umbRequestHelper.getApiUrl('imagesApiBaseUrl', 'GetBigThumbnail', [{ originalImagePath: property.value }]);
                            return thumbnailUrl;
                        } else {
                            return null;
                        }
                    } else {
                        return property.value;
                    }
                }
                return null;
            });
        }
    });
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.CropSizesController', function ($scope) {
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.editMode = false;
        $scope.setFocus = false;
        $scope.remove = function (item, evt) {
            evt.preventDefault();
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.alias === item.alias;
            });
        };
        $scope.edit = function (item, evt) {
            evt.preventDefault();
            $scope.editMode = true;
            $scope.setFocus = false;
            $scope.newItem = item;
        };
        $scope.cancel = function (evt) {
            evt.preventDefault();
            $scope.editMode = false;
            $scope.setFocus = true;
            $scope.newItem = null;
        };
        $scope.change = function () {
            // Listen to the change event and set focus 2 false
            if ($scope.setFocus) {
                $scope.setFocus = false;
                return;
            }
        };
        $scope.add = function (evt) {
            evt.preventDefault();
            $scope.editMode = false;
            $scope.setFocus = true;
            if ($scope.newItem && $scope.newItem.alias && angular.isNumber($scope.newItem.width) && angular.isNumber($scope.newItem.height) && $scope.newItem.width > 0 && $scope.newItem.height > 0) {
                var exists = _.find($scope.model.value, function (item) {
                    return $scope.newItem.alias === item.alias;
                });
                if (!exists) {
                    $scope.model.value.push($scope.newItem);
                    $scope.newItem = {};
                    $scope.hasError = false;
                    $scope.cropAdded = false;
                    return;
                } else {
                    $scope.newItem = null;
                    $scope.hasError = false;
                    return;
                }
            }
            //there was an error, do the highlight (will be set back by the directive)
            $scope.hasError = true;
        };
        $scope.sortableOptions = { axis: 'y' };
    });
    function includePropsPreValsController($rootScope, $scope, localizationService, contentTypeResource) {
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.hasError = false;
        $scope.errorMsg = '';
        $scope.propertyAliases = [];
        $scope.selectedField = null;
        $scope.systemFields = [
            { value: 'sortOrder' },
            { value: 'updateDate' },
            { value: 'updater' },
            { value: 'createDate' },
            { value: 'owner' },
            { value: 'published' },
            { value: 'contentTypeAlias' },
            { value: 'email' },
            { value: 'username' }
        ];
        $scope.getLocalizedKey = function (alias) {
            switch (alias) {
            case 'name':
                return 'general_name';
            case 'sortOrder':
                return 'general_sort';
            case 'updateDate':
                return 'content_updateDate';
            case 'updater':
                return 'content_updatedBy';
            case 'createDate':
                return 'content_createDate';
            case 'owner':
                return 'content_createBy';
            case 'published':
                return 'content_isPublished';
            case 'contentTypeAlias':
                //NOTE: This will just be 'Document' type even if it's for media/members since this is just a pre-val editor and we don't have a key for 'Content Type Alias'
                return 'content_documentType';
            case 'email':
                return 'general_email';
            case 'username':
                return 'general_username';
            }
            return alias;
        };
        $scope.changeField = function () {
            $scope.hasError = false;
            $scope.errorMsg = '';
        };
        $scope.removeField = function (e) {
            $scope.model.value = _.reject($scope.model.value, function (x) {
                return x.alias === e.alias;
            });
        };
        //now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
        _.each($scope.systemFields, function (e, i) {
            var key = $scope.getLocalizedKey(e.value);
            localizationService.localize(key).then(function (v) {
                e.name = v;
                switch (e.value) {
                case 'updater':
                    e.name += ' (Content only)';
                    break;
                case 'published':
                    e.name += ' (Content only)';
                    break;
                case 'email':
                    e.name += ' (Members only)';
                    break;
                case 'username':
                    e.name += ' (Members only)';
                    break;
                }
            });
        });
        // Return a helper with preserved width of cells
        var fixHelper = function (e, ui) {
            ui.children().each(function () {
                $(this).width($(this).width());
            });
            var row = ui.clone();
            row.css('background-color', 'lightgray');
            return row;
        };
        $scope.sortableOptions = {
            helper: fixHelper,
            handle: '.handle',
            opacity: 0.5,
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> tr',
            tolerance: 'pointer',
            forcePlaceholderSize: true,
            start: function (e, ui) {
                ui.placeholder.height(ui.item.height());
            },
            update: function (e, ui) {
                // Get the new and old index for the moved element (using the text as the identifier)
                var newIndex = ui.item.index();
                var movedAlias = $('.alias-value', ui.item).text().trim();
                var originalIndex = getAliasIndexByText(movedAlias);
                // Move the element in the model
                if (originalIndex > -1) {
                    var movedElement = $scope.model.value[originalIndex];
                    $scope.model.value.splice(originalIndex, 1);
                    $scope.model.value.splice(newIndex, 0, movedElement);
                }
            }
        };
        contentTypeResource.getAllPropertyTypeAliases().then(function (data) {
            $scope.propertyAliases = data;
        });
        $scope.addField = function () {
            var val = $scope.selectedField;
            if (val) {
                var isSystem = val.startsWith('_system_');
                if (isSystem) {
                    val = val.trimStart('_system_');
                }
                var exists = _.find($scope.model.value, function (i) {
                    return i.alias === val;
                });
                if (!exists) {
                    $scope.hasError = false;
                    $scope.errorMsg = '';
                    $scope.model.value.push({
                        alias: val,
                        isSystem: isSystem ? 1 : 0
                    });
                } else {
                    //there was an error, do the highlight (will be set back by the directive)
                    $scope.hasError = true;
                    $scope.errorMsg = 'Property is already added';
                }
            } else {
                $scope.hasError = true;
                $scope.errorMsg = 'No property selected';
            }
        };
        function getAliasIndexByText(value) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].alias === value) {
                    return i;
                }
            }
            return -1;
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.IncludePropertiesListViewController', includePropsPreValsController);
    /**
 * @ngdoc controller
 * @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController
 * @function
 *
 * @description
 * The controller for configuring layouts for list views
 */
    (function () {
        'use strict';
        function ListViewLayoutsPreValsController($scope) {
            var vm = this;
            vm.focusLayoutName = false;
            vm.layoutsSortableOptions = {
                distance: 10,
                tolerance: 'pointer',
                opacity: 0.7,
                scroll: true,
                cursor: 'move',
                handle: '.list-view-layout__sort-handle'
            };
            vm.addLayout = addLayout;
            vm.showPrompt = showPrompt;
            vm.hidePrompt = hidePrompt;
            vm.removeLayout = removeLayout;
            vm.openIconPicker = openIconPicker;
            function activate() {
            }
            function addLayout() {
                vm.focusLayoutName = false;
                var layout = {
                    'name': '',
                    'path': '',
                    'icon': 'icon-stop',
                    'selected': true
                };
                $scope.model.value.push(layout);
            }
            function showPrompt(layout) {
                layout.deletePrompt = true;
            }
            function hidePrompt(layout) {
                layout.deletePrompt = false;
            }
            function removeLayout($index, layout) {
                $scope.model.value.splice($index, 1);
            }
            function openIconPicker(layout) {
                vm.iconPickerDialog = {
                    view: 'iconpicker',
                    show: true,
                    submit: function (model) {
                        if (model.color) {
                            layout.icon = model.icon + ' ' + model.color;
                        } else {
                            layout.icon = model.icon;
                        }
                        vm.focusLayoutName = true;
                        vm.iconPickerDialog.show = false;
                        vm.iconPickerDialog = null;
                    }
                };
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.PrevalueEditors.ListViewLayoutsPreValsController', ListViewLayoutsPreValsController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.DocumentType.EditController
 * @function
 *
 * @description
 * The controller for the content type editor
 */
    (function () {
        'use strict';
        function ListViewGridLayoutController($scope, $routeParams, mediaHelper, mediaResource, $location, listViewHelper, mediaTypeHelper) {
            var vm = this;
            var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
            vm.nodeId = $scope.contentId;
            // Use whitelist of allowed file types if provided
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
            if (vm.acceptedFileTypes === '') {
                // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
            }
            vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
            vm.activeDrag = false;
            vm.mediaDetailsTooltip = {};
            vm.itemsWithoutFolders = [];
            vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
            vm.acceptedMediatypes = [];
            vm.dragEnter = dragEnter;
            vm.dragLeave = dragLeave;
            vm.onFilesQueue = onFilesQueue;
            vm.onUploadComplete = onUploadComplete;
            vm.hoverMediaItemDetails = hoverMediaItemDetails;
            vm.selectContentItem = selectContentItem;
            vm.selectItem = selectItem;
            vm.selectFolder = selectFolder;
            vm.goToItem = goToItem;
            function activate() {
                vm.itemsWithoutFolders = filterOutFolders($scope.items);
                //no need to make another REST/DB call if this data is not used when we are browsing the bin
                if ($scope.entityType === 'media' && !vm.isRecycleBin) {
                    mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) {
                        vm.acceptedMediatypes = types;
                    });
                }
            }
            function filterOutFolders(items) {
                var newArray = [];
                if (items && items.length) {
                    for (var i = 0; items.length > i; i++) {
                        var item = items[i];
                        var isFolder = !mediaHelper.hasFilePropertyType(item);
                        if (!isFolder) {
                            newArray.push(item);
                        }
                    }
                }
                return newArray;
            }
            function dragEnter(el, event) {
                vm.activeDrag = true;
            }
            function dragLeave(el, event) {
                vm.activeDrag = false;
            }
            function onFilesQueue() {
                vm.activeDrag = false;
            }
            function onUploadComplete() {
                $scope.getContent($scope.contentId);
            }
            function hoverMediaItemDetails(item, event, hover) {
                if (hover && !vm.mediaDetailsTooltip.show) {
                    vm.mediaDetailsTooltip.event = event;
                    vm.mediaDetailsTooltip.item = item;
                    vm.mediaDetailsTooltip.show = true;
                } else if (!hover && vm.mediaDetailsTooltip.show) {
                    vm.mediaDetailsTooltip.show = false;
                }
            }
            function selectContentItem(item, $event, $index) {
                listViewHelper.selectHandler(item, $index, $scope.items, $scope.selection, $event);
            }
            function selectItem(item, $event, $index) {
                listViewHelper.selectHandler(item, $index, vm.itemsWithoutFolders, $scope.selection, $event);
            }
            function selectFolder(folder, $event, $index) {
                listViewHelper.selectHandler(folder, $index, $scope.folders, $scope.selection, $event);
            }
            function goToItem(item, $event, $index) {
                $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + item.id);
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.GridLayoutController', ListViewGridLayoutController);
    }());
    (function () {
        'use strict';
        function ListViewListLayoutController($scope, listViewHelper, $location, mediaHelper, mediaTypeHelper) {
            var vm = this;
            var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
            vm.nodeId = $scope.contentId;
            // Use whitelist of allowed file types if provided
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(umbracoSettings.allowedUploadFiles);
            if (vm.acceptedFileTypes === '') {
                // If not provided, we pass in a blacklist by adding ! to the file extensions, allowing everything EXCEPT for disallowedUploadFiles
                vm.acceptedFileTypes = !mediaHelper.formatFileTypes(umbracoSettings.disallowedUploadFiles);
            }
            vm.maxFileSize = umbracoSettings.maxFileSize + 'KB';
            vm.activeDrag = false;
            vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20';
            vm.acceptedMediatypes = [];
            vm.selectItem = selectItem;
            vm.clickItem = clickItem;
            vm.selectAll = selectAll;
            vm.isSelectedAll = isSelectedAll;
            vm.isSortDirection = isSortDirection;
            vm.sort = sort;
            vm.dragEnter = dragEnter;
            vm.dragLeave = dragLeave;
            vm.onFilesQueue = onFilesQueue;
            vm.onUploadComplete = onUploadComplete;
            markAsSensitive();
            function activate() {
                if ($scope.entityType === 'media') {
                    mediaTypeHelper.getAllowedImagetypes(vm.nodeId).then(function (types) {
                        vm.acceptedMediatypes = types;
                    });
                }
            }
            function selectAll($event) {
                listViewHelper.selectAllItems($scope.items, $scope.selection, $event);
            }
            function isSelectedAll() {
                return listViewHelper.isSelectedAll($scope.items, $scope.selection);
            }
            function selectItem(selectedItem, $index, $event) {
                listViewHelper.selectHandler(selectedItem, $index, $scope.items, $scope.selection, $event);
            }
            function clickItem(item) {
                // if item.id is 2147483647 (int.MaxValue) use item.key
                $location.path($scope.entityType + '/' + $scope.entityType + '/edit/' + (item.id === 2147483647 ? item.key : item.id));
            }
            function isSortDirection(col, direction) {
                return listViewHelper.setSortingDirection(col, direction, $scope.options);
            }
            function sort(field, allow, isSystem) {
                if (allow) {
                    $scope.options.orderBySystemField = isSystem;
                    listViewHelper.setSorting(field, allow, $scope.options);
                    $scope.getContent($scope.contentId);
                }
            }
            // Dropzone upload functions
            function dragEnter(el, event) {
                vm.activeDrag = true;
            }
            function dragLeave(el, event) {
                vm.activeDrag = false;
            }
            function onFilesQueue() {
                vm.activeDrag = false;
            }
            function onUploadComplete() {
                $scope.getContent($scope.contentId);
            }
            function markAsSensitive() {
                angular.forEach($scope.options.includeProperties, function (option) {
                    option.isSensitive = false;
                    angular.forEach($scope.items, function (item) {
                        angular.forEach(item.properties, function (property) {
                            if (option.alias === property.alias) {
                                option.isSensitive = property.isSensitive;
                            }
                        });
                    });
                });
            }
            activate();
        }
        angular.module('umbraco').controller('Umbraco.PropertyEditors.ListView.ListLayoutController', ListViewListLayoutController);
    }());
    function listViewController($rootScope, $scope, $routeParams, $injector, $cookieStore, notificationsService, iconHelper, dialogService, editorState, localizationService, $location, appState, $timeout, $q, mediaResource, listViewHelper, userService, navigationService, treeService, mediaHelper) {
        //this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
        // that isn't created yet, if we continue this will use the parent id in the route params which isn't what
        // we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
        // the list view tab entirely when it's new.
        if ($routeParams.create) {
            $scope.isNew = true;
            return;
        }
        //Now we need to check if this is for media, members or content because that will depend on the resources we use
        var contentResource, getContentTypesCallback, getListResultsCallback, deleteItemCallback, getIdCallback, createEditUrlCallback;
        //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
        if ($scope.model.config.entityType && $scope.model.config.entityType === 'member' || appState.getSectionState('currentSection') === 'member') {
            $scope.entityType = 'member';
            contentResource = $injector.get('memberResource');
            getContentTypesCallback = $injector.get('memberTypeResource').getTypes;
            getListResultsCallback = contentResource.getPagedResults;
            deleteItemCallback = contentResource.deleteByKey;
            getIdCallback = function (selected) {
                return selected.key;
            };
            createEditUrlCallback = function (item) {
                return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.key + '?page=' + $scope.options.pageNumber + '&listName=' + $scope.contentId;
            };
        } else {
            //check the config for the entity type, or the current section name (since the config is only set in c#, not in pre-vals)
            if ($scope.model.config.entityType && $scope.model.config.entityType === 'media' || appState.getSectionState('currentSection') === 'media') {
                $scope.entityType = 'media';
                contentResource = $injector.get('mediaResource');
                getContentTypesCallback = $injector.get('mediaTypeResource').getAllowedTypes;
            } else {
                $scope.entityType = 'content';
                contentResource = $injector.get('contentResource');
                getContentTypesCallback = $injector.get('contentTypeResource').getAllowedTypes;
            }
            getListResultsCallback = contentResource.getChildren;
            deleteItemCallback = contentResource.deleteById;
            getIdCallback = function (selected) {
                return selected.id;
            };
            createEditUrlCallback = function (item) {
                return '/' + $scope.entityType + '/' + $scope.entityType + '/edit/' + item.id + '?page=' + $scope.options.pageNumber;
            };
        }
        $scope.pagination = [];
        $scope.isNew = false;
        $scope.actionInProgress = false;
        $scope.selection = [];
        $scope.folders = [];
        $scope.listViewResultSet = {
            totalPages: 0,
            items: []
        };
        $scope.createAllowedButtonSingle = false;
        $scope.createAllowedButtonSingleWithBlueprints = false;
        $scope.createAllowedButtonMultiWithBlueprints = false;
        //when this is null, we don't check permissions
        $scope.currentNodePermissions = null;
        if ($scope.entityType === 'content') {
            //Just ensure we do have an editorState
            if (editorState.current) {
                //Fetch current node allowed actions for the current user
                //This is the current node & not each individual child node in the list
                var currentUserPermissions = editorState.current.allowedActions;
                //Create a nicer model rather than the funky & hard to remember permissions strings
                $scope.currentNodePermissions = {
                    'canCopy': _.contains(currentUserPermissions, 'O'),
                    //Magic Char = O
                    'canCreate': _.contains(currentUserPermissions, 'C'),
                    //Magic Char = C
                    'canDelete': _.contains(currentUserPermissions, 'D'),
                    //Magic Char = D
                    'canMove': _.contains(currentUserPermissions, 'M'),
                    //Magic Char = M                
                    'canPublish': _.contains(currentUserPermissions, 'U'),
                    //Magic Char = U
                    'canUnpublish': _.contains(currentUserPermissions, 'U')    //Magic Char = Z (however UI says it can't be set, so if we can publish 'U' we can unpublish)
                };
            }
        }
        //when this is null, we don't check permissions
        $scope.buttonPermissions = null;
        //When we are dealing with 'content', we need to deal with permissions on child nodes.
        // Currently there is no real good way to 
        if ($scope.entityType === 'content') {
            var idsWithPermissions = null;
            $scope.buttonPermissions = {
                canCopy: true,
                canCreate: true,
                canDelete: true,
                canMove: true,
                canPublish: true,
                canUnpublish: true
            };
            $scope.$watch(function () {
                return $scope.selection.length;
            }, function (newVal, oldVal) {
                if (idsWithPermissions == null && newVal > 0 || idsWithPermissions != null) {
                    //get all of the selected ids
                    var ids = _.map($scope.selection, function (i) {
                        return i.id.toString();
                    });
                    //remove the dictionary items that don't have matching ids
                    var filtered = {};
                    _.each(idsWithPermissions, function (value, key, list) {
                        if (_.contains(ids, key)) {
                            filtered[key] = value;
                        }
                    });
                    idsWithPermissions = filtered;
                    //find all ids that we haven't looked up permissions for
                    var existingIds = _.keys(idsWithPermissions);
                    var missingLookup = _.map(_.difference(ids, existingIds), function (i) {
                        return Number(i);
                    });
                    if (missingLookup.length > 0) {
                        contentResource.getPermissions(missingLookup).then(function (p) {
                            $scope.buttonPermissions = listViewHelper.getButtonPermissions(p, idsWithPermissions);
                        });
                    } else {
                        $scope.buttonPermissions = listViewHelper.getButtonPermissions({}, idsWithPermissions);
                    }
                }
            });
        }
        $scope.options = {
            displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1,
            pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10,
            pageNumber: $routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0 ? $routeParams.page : 1,
            filter: '',
            orderBy: ($scope.model.config.orderBy ? $scope.model.config.orderBy : 'VersionDate').trim(),
            orderDirection: $scope.model.config.orderDirection ? $scope.model.config.orderDirection.trim() : 'desc',
            orderBySystemField: true,
            includeProperties: $scope.model.config.includeProperties ? $scope.model.config.includeProperties : [
                {
                    alias: 'updateDate',
                    header: 'Last edited',
                    isSystem: 1
                },
                {
                    alias: 'updater',
                    header: 'Last edited by',
                    isSystem: 1
                }
            ],
            layout: {
                layouts: $scope.model.config.layouts,
                activeLayout: listViewHelper.getLayout($routeParams.id, $scope.model.config.layouts)
            },
            allowBulkPublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkPublish,
            allowBulkUnpublish: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkUnpublish,
            allowBulkCopy: $scope.entityType === 'content' && $scope.model.config.bulkActionPermissions.allowBulkCopy,
            allowBulkMove: $scope.model.config.bulkActionPermissions.allowBulkMove,
            allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete
        };
        // Check if selected order by field is actually custom field
        for (var j = 0; j < $scope.options.includeProperties.length; j++) {
            var includedProperty = $scope.options.includeProperties[j];
            if (includedProperty.alias.toLowerCase() === $scope.options.orderBy.toLowerCase()) {
                $scope.options.orderBySystemField = includedProperty.isSystem === 1;
                break;
            }
        }
        //update all of the system includeProperties to enable sorting
        _.each($scope.options.includeProperties, function (e, i) {
            //NOTE: special case for contentTypeAlias, it's a system property that cannot be sorted
            // to do that, we'd need to update the base query for content to include the content type alias column
            // which requires another join and would be slower. BUT We are doing this for members so not sure it makes a diff?
            if (e.alias != 'contentTypeAlias') {
                e.allowSorting = true;
            }
            // Another special case for members, only fields on the base table (cmsMember) can be used for sorting
            if (e.isSystem && $scope.entityType == 'member') {
                e.allowSorting = e.alias == 'username' || e.alias == 'email';
            }
            if (e.isSystem) {
                //localize the header
                var key = getLocalizedKey(e.alias);
                localizationService.localize(key).then(function (v) {
                    e.header = v;
                });
            }
        });
        $scope.selectLayout = function (selectedLayout) {
            $scope.options.layout.activeLayout = listViewHelper.setLayout($routeParams.id, selectedLayout, $scope.model.config.layouts);
        };
        function showNotificationsAndReset(err, reload, successMsg) {
            //check if response is ysod
            if (err.status && err.status >= 500) {
                // Open ysod overlay
                $scope.ysodOverlay = {
                    view: 'ysod',
                    error: err,
                    show: true
                };
            }
            $timeout(function () {
                $scope.bulkStatus = '';
                $scope.actionInProgress = false;
            }, 500);
            if (err.data && angular.isArray(err.data.notifications)) {
                for (var i = 0; i < err.data.notifications.length; i++) {
                    notificationsService.showNotification(err.data.notifications[i]);
                }
            } else if (successMsg) {
                localizationService.localize('bulk_done').then(function (v) {
                    notificationsService.success(v, successMsg);
                });
            }
        }
        $scope.next = function (pageNumber) {
            $scope.options.pageNumber = pageNumber;
            $scope.reloadView($scope.contentId);
        };
        $scope.goToPage = function (pageNumber) {
            $scope.options.pageNumber = pageNumber;
            $scope.reloadView($scope.contentId);
        };
        $scope.prev = function (pageNumber) {
            $scope.options.pageNumber = pageNumber;
            $scope.reloadView($scope.contentId);
        };
        /*Loads the search results, based on parameters set in prev,next,sort and so on*/
        /*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
   with simple values */
        $scope.getContent = function () {
            $scope.reloadView($scope.contentId);
        };
        $scope.reloadView = function (id) {
            $scope.viewLoaded = false;
            $scope.folders = [];
            listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
            getListResultsCallback(id, $scope.options).then(function (data) {
                $scope.actionInProgress = false;
                $scope.listViewResultSet = data;
                //update all values for display
                var section = appState.getSectionState('currentSection');
                if ($scope.listViewResultSet.items) {
                    _.each($scope.listViewResultSet.items, function (e, index) {
                        setPropertyValues(e);
                        // create the folders collection (only for media list views)
                        if (section === 'media' && !mediaHelper.hasFilePropertyType(e)) {
                            $scope.folders.push(e);
                        }
                    });
                }
                $scope.viewLoaded = true;
                //NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
                // if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last
                // available page and then re-load again
                if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
                    $scope.options.pageNumber = $scope.listViewResultSet.totalPages;
                    //reload!
                    $scope.reloadView(id);
                }
            });
        };
        var searchListView = _.debounce(function () {
            $scope.$apply(function () {
                makeSearch();
            });
        }, 500);
        $scope.forceSearch = function (ev) {
            //13: enter
            switch (ev.keyCode) {
            case 13:
                makeSearch();
                break;
            }
        };
        $scope.enterSearch = function () {
            $scope.viewLoaded = false;
            searchListView();
        };
        function makeSearch() {
            if ($scope.options.filter !== null && $scope.options.filter !== undefined) {
                $scope.options.pageNumber = 1;
                $scope.reloadView($scope.contentId);
            }
        }
        $scope.isAnythingSelected = function () {
            if ($scope.selection.length === 0) {
                return false;
            } else {
                return true;
            }
        };
        $scope.selectedItemsCount = function () {
            return $scope.selection.length;
        };
        $scope.clearSelection = function () {
            listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
        };
        $scope.getIcon = function (entry) {
            return iconHelper.convertFromLegacyIcon(entry.icon);
        };
        function serial(selected, fn, getStatusMsg, index) {
            return fn(selected, index).then(function (content) {
                index++;
                $scope.bulkStatus = getStatusMsg(index, selected.length);
                return index < selected.length ? serial(selected, fn, getStatusMsg, index) : content;
            }, function (err) {
                var reload = index > 0;
                showNotificationsAndReset(err, reload);
                return err;
            });
        }
        function applySelected(fn, getStatusMsg, getSuccessMsg, confirmMsg) {
            var selected = $scope.selection;
            if (selected.length === 0)
                return;
            if (confirmMsg && !confirm(confirmMsg))
                return;
            $scope.actionInProgress = true;
            $scope.bulkStatus = getStatusMsg(0, selected.length);
            return serial(selected, fn, getStatusMsg, 0).then(function (result) {
                // executes once the whole selection has been processed
                // in case of an error (caught by serial), result will be the error
                if (!(result.data && angular.isArray(result.data.notifications)))
                    showNotificationsAndReset(result, true, getSuccessMsg(selected.length));
            });
        }
        $scope.delete = function () {
            var confirmDeleteText = '';
            localizationService.localize('defaultdialogs_confirmdelete').then(function (value) {
                confirmDeleteText = value;
                var attempt = applySelected(function (selected, index) {
                    return deleteItemCallback(getIdCallback(selected[index]));
                }, function (count, total) {
                    var key = total === 1 ? 'bulk_deletedItemOfItem' : 'bulk_deletedItemOfItems';
                    return localizationService.localize(key, [
                        count,
                        total
                    ]);
                }, function (total) {
                    var key = total === 1 ? 'bulk_deletedItem' : 'bulk_deletedItems';
                    return localizationService.localize(key, [total]);
                }, confirmDeleteText + '?');
                if (attempt) {
                    attempt.then(function () {
                        //executes if all is successful, let's sync the tree
                        var activeNode = appState.getTreeState('selectedNode');
                        if (activeNode) {
                            navigationService.reloadNode(activeNode);
                        }
                        $scope.getContent();
                    });
                }
            });
        };
        $scope.publish = function () {
            applySelected(function (selected, index) {
                return contentResource.publishById(getIdCallback(selected[index]));
            }, function (count, total) {
                var key = total === 1 ? 'bulk_publishedItemOfItem' : 'bulk_publishedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_publishedItem' : 'bulk_publishedItems';
                return localizationService.localize(key, [total]);
            });
        };
        $scope.unpublish = function () {
            applySelected(function (selected, index) {
                return contentResource.unPublish(getIdCallback(selected[index]));
            }, function (count, total) {
                var key = total === 1 ? 'bulk_unpublishedItemOfItem' : 'bulk_unpublishedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_unpublishedItem' : 'bulk_unpublishedItems';
                return localizationService.localize(key, [total]);
            });
        };
        $scope.move = function () {
            $scope.moveDialog = {};
            $scope.moveDialog.title = localizationService.localize('general_move');
            $scope.moveDialog.section = $scope.entityType;
            $scope.moveDialog.currentNode = $scope.contentId;
            $scope.moveDialog.view = 'move';
            $scope.moveDialog.show = true;
            $scope.moveDialog.submit = function (model) {
                if (model.target) {
                    performMove(model.target);
                }
                $scope.moveDialog.show = false;
                $scope.moveDialog = null;
            };
            $scope.moveDialog.close = function (oldModel) {
                $scope.moveDialog.show = false;
                $scope.moveDialog = null;
            };
        };
        function performMove(target) {
            //NOTE: With the way this applySelected/serial works, I'm not sure there's a better way currently to return 
            // a specific value from one of the methods, so we'll have to try this way. Even though the first method
            // will fire once per every node moved, the destination path will be the same and we need to use that to sync.
            var newPath = null;
            applySelected(function (selected, index) {
                return contentResource.move({
                    parentId: target.id,
                    id: getIdCallback(selected[index])
                }).then(function (path) {
                    newPath = path;
                    return path;
                });
            }, function (count, total) {
                var key = total === 1 ? 'bulk_movedItemOfItem' : 'bulk_movedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_movedItem' : 'bulk_movedItems';
                return localizationService.localize(key, [total]);
            }).then(function () {
                //executes if all is successful, let's sync the tree
                if (newPath) {
                    //we need to do a double sync here: first refresh the node where the content was moved,
                    // then refresh the node where the content was moved from
                    navigationService.syncTree({
                        tree: target.nodeType ? target.nodeType : target.metaData.treeAlias,
                        path: newPath,
                        forceReload: true,
                        activate: false
                    }).then(function (args) {
                        //get the currently edited node (if any)
                        var activeNode = appState.getTreeState('selectedNode');
                        if (activeNode) {
                            navigationService.reloadNode(activeNode);
                        }
                    });
                }
            });
        }
        $scope.copy = function () {
            $scope.copyDialog = {};
            $scope.copyDialog.title = localizationService.localize('general_copy');
            $scope.copyDialog.section = $scope.entityType;
            $scope.copyDialog.currentNode = $scope.contentId;
            $scope.copyDialog.view = 'copy';
            $scope.copyDialog.show = true;
            $scope.copyDialog.submit = function (model) {
                if (model.target) {
                    performCopy(model.target, model.relateToOriginal);
                }
                $scope.copyDialog.show = false;
                $scope.copyDialog = null;
            };
            $scope.copyDialog.close = function (oldModel) {
                $scope.copyDialog.show = false;
                $scope.copyDialog = null;
            };
        };
        function performCopy(target, relateToOriginal) {
            applySelected(function (selected, index) {
                return contentResource.copy({
                    parentId: target.id,
                    id: getIdCallback(selected[index]),
                    relateToOriginal: relateToOriginal
                });
            }, function (count, total) {
                var key = total === 1 ? 'bulk_copiedItemOfItem' : 'bulk_copiedItemOfItems';
                return localizationService.localize(key, [
                    count,
                    total
                ]);
            }, function (total) {
                var key = total === 1 ? 'bulk_copiedItem' : 'bulk_copiedItems';
                return localizationService.localize(key, [total]);
            });
        }
        function getCustomPropertyValue(alias, properties) {
            var value = '';
            var index = 0;
            var foundAlias = false;
            for (var i = 0; i < properties.length; i++) {
                if (properties[i].alias == alias) {
                    foundAlias = true;
                    break;
                }
                index++;
            }
            if (foundAlias) {
                value = properties[index].value;
            }
            return value;
        }
        /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */
        function setPropertyValues(result) {
            //set the edit url
            result.editPath = createEditUrlCallback(result);
            _.each($scope.options.includeProperties, function (e, i) {
                var alias = e.alias;
                // First try to pull the value directly from the alias (e.g. updatedBy)
                var value = result[alias];
                // If this returns an object, look for the name property of that (e.g. owner.name)
                if (value === Object(value)) {
                    value = value['name'];
                }
                // If we've got nothing yet, look at a user defined property
                if (typeof value === 'undefined') {
                    value = getCustomPropertyValue(alias, result.properties);
                }
                // If we have a date, format it
                if (isDate(value)) {
                    value = value.substring(0, value.length - 3);
                }
                // set what we've got on the result
                result[alias] = value;
            });
        }
        function isDate(val) {
            if (angular.isString(val)) {
                return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/);
            }
            return false;
        }
        function initView() {
            //default to root id if the id is undefined
            var id = $routeParams.id;
            if (id === undefined) {
                id = -1;
            }
            //$scope.listViewAllowedTypes = getContentTypesCallback(id);
            getContentTypesCallback(id).then(function (listViewAllowedTypes) {
                $scope.listViewAllowedTypes = listViewAllowedTypes;
                var blueprints = false;
                _.each(listViewAllowedTypes, function (allowedType) {
                    if (_.isEmpty(allowedType.blueprints)) {
                        // this helps the view understand that there are no blueprints available
                        allowedType.blueprints = null;
                    } else {
                        blueprints = true;
                    }
                });
                if (listViewAllowedTypes.length === 1 && blueprints === false) {
                    $scope.createAllowedButtonSingle = true;
                }
                if (listViewAllowedTypes.length === 1 && blueprints === true) {
                    $scope.createAllowedButtonSingleWithBlueprints = true;
                }
                if (listViewAllowedTypes.length > 1) {
                    $scope.createAllowedButtonMultiWithBlueprints = true;
                }
            });
            $scope.contentId = id;
            $scope.isTrashed = id === '-20' || id === '-21';
            $scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed;
            $scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed;
            $scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish || $scope.options.allowBulkUnpublish || $scope.options.allowBulkCopy || $scope.options.allowBulkMove || $scope.options.allowBulkDelete;
            $scope.reloadView($scope.contentId);
        }
        function getLocalizedKey(alias) {
            switch (alias) {
            case 'sortOrder':
                return 'general_sort';
            case 'updateDate':
                return 'content_updateDate';
            case 'updater':
                return 'content_updatedBy';
            case 'createDate':
                return 'content_createDate';
            case 'owner':
                return 'content_createBy';
            case 'published':
                return 'content_isPublished';
            case 'contentTypeAlias':
                //TODO: Check for members
                return $scope.entityType === 'content' ? 'content_documentType' : 'content_mediatype';
            case 'email':
                return 'general_email';
            case 'username':
                return 'general_username';
            }
            return alias;
        }
        function getItemKey(itemId) {
            for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
                var item = $scope.listViewResultSet.items[i];
                if (item.id === itemId) {
                    return item.key;
                }
            }
        }
        function createBlank(entityType, docTypeAlias) {
            $location.path('/' + entityType + '/' + entityType + '/edit/' + $scope.contentId).search('doctype=' + docTypeAlias + '&create=true');
        }
        function createFromBlueprint(entityType, docTypeAlias, blueprintId) {
            $location.path('/' + entityType + '/' + entityType + '/edit/' + $scope.contentId).search('doctype=' + docTypeAlias + '&create=true&blueprintId=' + blueprintId);
        }
        $scope.createBlank = createBlank;
        $scope.createFromBlueprint = createFromBlueprint;
        //GO!
        initView();
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ListViewController', listViewController);
    function sortByPreValsController($rootScope, $scope, localizationService, editorState, listViewPrevalueHelper) {
        //Get the prevalue from the correct place
        function getPrevalues() {
            if (editorState.current.preValues) {
                return editorState.current.preValues;
            } else {
                return listViewPrevalueHelper.getPrevalues();
            }
        }
        //Watch the prevalues
        $scope.$watch(function () {
            return _.findWhere(getPrevalues(), { key: 'includeProperties' }).value;
        }, function () {
            populateFields();
        }, true);
        //Use deep watching, otherwise we won't pick up header changes
        function populateFields() {
            // Helper to find a particular value from the list of sort by options
            function findFromSortByFields(value) {
                return _.find($scope.sortByFields, function (e) {
                    return e.value.toLowerCase() === value.toLowerCase();
                });
            }
            // Get list of properties assigned as columns of the list view
            var propsPreValue = _.findWhere(getPrevalues(), { key: 'includeProperties' });
            // Populate list of options for the default sort (all the columns plus then node name)
            $scope.sortByFields = [];
            $scope.sortByFields.push({
                value: 'name',
                name: 'Name',
                isSystem: 1
            });
            if (propsPreValue != undefined) {
                for (var i = 0; i < propsPreValue.value.length; i++) {
                    var value = propsPreValue.value[i];
                    $scope.sortByFields.push({
                        value: value.alias,
                        name: value.header,
                        isSystem: value.isSystem
                    });
                }
            }
            // Localize the system fields, for some reason the directive doesn't work inside of the select group with an ng-model declared
            var systemFields = [
                {
                    value: 'SortOrder',
                    key: 'general_sort'
                },
                {
                    value: 'Name',
                    key: 'general_name'
                },
                {
                    value: 'VersionDate',
                    key: 'content_updateDate'
                },
                {
                    value: 'Updater',
                    key: 'content_updatedBy'
                },
                {
                    value: 'CreateDate',
                    key: 'content_createDate'
                },
                {
                    value: 'Owner',
                    key: 'content_createBy'
                },
                {
                    value: 'ContentTypeAlias',
                    key: 'content_documentType'
                },
                {
                    value: 'Published',
                    key: 'content_isPublished'
                },
                {
                    value: 'Email',
                    key: 'general_email'
                },
                {
                    value: 'Username',
                    key: 'general_username'
                }
            ];
            _.each(systemFields, function (e) {
                localizationService.localize(e.key).then(function (v) {
                    var sortByListValue = findFromSortByFields(e.value);
                    if (sortByListValue) {
                        sortByListValue.name = v;
                        switch (e.value) {
                        case 'Updater':
                            e.name += ' (Content only)';
                            break;
                        case 'Published':
                            e.name += ' (Content only)';
                            break;
                        case 'Email':
                            e.name += ' (Members only)';
                            break;
                        case 'Username':
                            e.name += ' (Members only)';
                            break;
                        }
                    }
                });
            });
            // Check existing model value is available in list and ensure a value is set
            var existingValue = findFromSortByFields($scope.model.value);
            if (existingValue) {
                // Set the existing value
                // The old implementation pre Umbraco 7.5 used PascalCase aliases, this uses camelCase, so this ensures that any previous value is set
                $scope.model.value = existingValue.value;
            } else {
                // Existing value not found, set to first value
                $scope.model.value = $scope.sortByFields[0].value;
            }
        }
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.SortByListViewController', sortByPreValsController);
    //DO NOT DELETE THIS, this is in use... 
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MacroContainerController', function ($scope, dialogService, entityResource, macroService) {
        $scope.renderModel = [];
        $scope.allowOpenButton = true;
        $scope.allowRemoveButton = true;
        $scope.sortableOptions = {};
        if ($scope.model.value) {
            var macros = $scope.model.value.split('>');
            angular.forEach(macros, function (syntax, key) {
                if (syntax && syntax.length > 10) {
                    //re-add the char we split on
                    syntax = syntax + '>';
                    var parsed = macroService.parseMacroSyntax(syntax);
                    if (!parsed) {
                        parsed = {};
                    }
                    parsed.syntax = syntax;
                    collectDetails(parsed);
                    $scope.renderModel.push(parsed);
                    setSortingState($scope.renderModel);
                }
            });
        }
        function collectDetails(macro) {
            macro.details = '';
            macro.icon = 'icon-settings-alt';
            if (macro.macroParamsDictionary) {
                angular.forEach(macro.macroParamsDictionary, function (value, key) {
                    macro.details += key + ': ' + value + ' ';
                });
            }
        }
        function openDialog(index) {
            var dialogData = { allowedMacros: $scope.model.config.allowed };
            if (index !== null && $scope.renderModel[index]) {
                var macro = $scope.renderModel[index];
                dialogData['macroData'] = macro;
            }
            $scope.macroPickerOverlay = {};
            $scope.macroPickerOverlay.view = 'macropicker';
            $scope.macroPickerOverlay.dialogData = dialogData;
            $scope.macroPickerOverlay.show = true;
            $scope.macroPickerOverlay.submit = function (model) {
                var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                collectDetails(macroObject);
                //update the raw syntax and the list...
                if (index !== null && $scope.renderModel[index]) {
                    $scope.renderModel[index] = macroObject;
                } else {
                    $scope.renderModel.push(macroObject);
                }
                setSortingState($scope.renderModel);
                $scope.macroPickerOverlay.show = false;
                $scope.macroPickerOverlay = null;
            };
            $scope.macroPickerOverlay.close = function (oldModel) {
                $scope.macroPickerOverlay.show = false;
                $scope.macroPickerOverlay = null;
            };
        }
        $scope.edit = function (index) {
            openDialog(index);
        };
        $scope.add = function () {
            if ($scope.model.config.max && $scope.model.config.max > 0 && $scope.renderModel.length >= $scope.model.config.max) {
                //cannot add more than the max
                return;
            }
            openDialog();
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
            setSortingState($scope.renderModel);
        };
        $scope.clear = function () {
            $scope.model.value = '';
            $scope.renderModel = [];
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var syntax = [];
            angular.forEach($scope.renderModel, function (value, key) {
                syntax.push(value.syntax);
            });
            $scope.model.value = syntax.join('');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        function setSortingState(items) {
            // disable sorting if the list only consist of one item
            if (items.length > 1) {
                $scope.sortableOptions.disabled = false;
            } else {
                $scope.sortableOptions.disabled = true;
            }
        }
    });
    function MacroListController($scope, entityResource) {
        $scope.items = [];
        entityResource.getAll('Macro').then(function (items) {
            _.each(items, function (i) {
                $scope.items.push({
                    name: i.name,
                    alias: i.alias
                });
            });
        });
    }
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.MacroList', MacroListController);
    //inject umbracos assetsServce and dialog service
    function MarkdownEditorController($scope, $element, assetsService, dialogService, angularHelper, $timeout) {
        //tell the assets service to load the markdown.editor libs from the markdown editors
        //plugin folder
        if ($scope.model.value === null || $scope.model.value === '') {
            $scope.model.value = $scope.model.config.defaultValue;
        }
        function openMediaPicker(callback) {
            $scope.mediaPickerOverlay = {};
            $scope.mediaPickerOverlay.view = 'mediaPicker';
            $scope.mediaPickerOverlay.show = true;
            $scope.mediaPickerOverlay.disableFolderSelect = true;
            $scope.mediaPickerOverlay.submit = function (model) {
                var selectedImagePath = model.selectedImages[0].image;
                callback(selectedImagePath);
                $scope.mediaPickerOverlay.show = false;
                $scope.mediaPickerOverlay = null;
            };
            $scope.mediaPickerOverlay.close = function (model) {
                $scope.mediaPickerOverlay.show = false;
                $scope.mediaPickerOverlay = null;
            };
        }
        assetsService.load([
            'lib/markdown/markdown.converter.js',
            'lib/markdown/markdown.sanitizer.js',
            'lib/markdown/markdown.editor.js'
        ]).then(function () {
            // we need a short delay to wait for the textbox to appear.
            setTimeout(function () {
                //this function will execute when all dependencies have loaded
                // but in the case that they've been previously loaded, we can only
                // init the md editor after this digest because the DOM needs to be ready first
                // so run the init on a timeout
                $timeout(function () {
                    $scope.markdownEditorInitComplete = false;
                    var converter2 = new Markdown.Converter();
                    var editor2 = new Markdown.Editor(converter2, '-' + $scope.model.alias);
                    editor2.run();
                    //subscribe to the image dialog clicks
                    editor2.hooks.set('insertImageDialog', function (callback) {
                        openMediaPicker(callback);
                        return true;    // tell the editor that we'll take care of getting the image url
                    });
                    editor2.hooks.set('onPreviewRefresh', function () {
                        // We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library.
                        if ($scope.model.value !== $('textarea', $element).val()) {
                            if ($scope.markdownEditorInitComplete) {
                                //only set dirty after init load to avoid "unsaved" dialogue when we don't want it
                                angularHelper.getCurrentForm($scope).$setDirty();
                            } else {
                                $scope.markdownEditorInitComplete = true;
                            }
                            $scope.model.value = $('textarea', $element).val();
                        }
                    });
                }, 200);
            });
            //load the seperat css for the editor to avoid it blocking our js loading TEMP HACK
            assetsService.loadCss('lib/markdown/markdown.css', $scope);
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MarkdownEditorController', MarkdownEditorController);
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MediaPickerController', function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) {
        //check the pre-values for multi-picker
        var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false;
        var onlyImages = $scope.model.config.onlyImages && $scope.model.config.onlyImages !== '0' ? true : false;
        var disableFolderSelect = $scope.model.config.disableFolderSelect && $scope.model.config.disableFolderSelect !== '0' ? true : false;
        if (!$scope.model.config.startNodeId) {
            userService.getCurrentUser().then(function (userData) {
                $scope.model.config.startNodeId = userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0];
                $scope.model.config.startNodeIsVirtual = userData.startMediaIds.length !== 1;
            });
        }
        function setupViewModel() {
            $scope.mediaItems = [];
            $scope.ids = [];
            $scope.isMultiPicker = multiPicker;
            if ($scope.model.value) {
                var ids = $scope.model.value.split(',');
                //NOTE: We need to use the entityResource NOT the mediaResource here because
                // the mediaResource has server side auth configured for which the user must have
                // access to the media section, if they don't they'll get auth errors. The entityResource
                // acts differently in that it allows access if the user has access to any of the apps that
                // might require it's use. Therefore we need to use the metaData property to get at the thumbnail
                // value.
                entityResource.getByIds(ids, 'Media').then(function (medias) {
                    // The service only returns item results for ids that exist (deleted items are silently ignored).
                    // This results in the picked items value to be set to contain only ids of picked items that could actually be found.
                    // Since a referenced item could potentially be restored later on, instead of changing the selected values here based
                    // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently
                    // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending
                    // on whether it is simply resaved or not.
                    // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders
                    // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before.
                    medias = _.map(ids, function (id) {
                        var found = _.find(medias, function (m) {
                            // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and
                            // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString()
                            // compares and be completely sure it works.
                            return m.udi.toString() === id.toString() || m.id.toString() === id.toString();
                        });
                        if (found) {
                            return found;
                        } else {
                            return {
                                name: localizationService.dictionary.mediaPicker_deletedItem,
                                id: $scope.model.config.idType !== 'udi' ? id : null,
                                udi: $scope.model.config.idType === 'udi' ? id : null,
                                icon: 'icon-picture',
                                thumbnail: null,
                                trashed: true
                            };
                        }
                    });
                    _.each(medias, function (media, i) {
                        // if there is no thumbnail, try getting one if the media is not a placeholder item
                        if (!media.thumbnail && media.id && media.metaData) {
                            media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                        }
                        $scope.mediaItems.push(media);
                        if ($scope.model.config.idType === 'udi') {
                            $scope.ids.push(media.udi);
                        } else {
                            $scope.ids.push(media.id);
                        }
                    });
                    $scope.sync();
                });
            }
        }
        setupViewModel();
        $scope.remove = function (index) {
            $scope.mediaItems.splice(index, 1);
            $scope.ids.splice(index, 1);
            $scope.sync();
        };
        $scope.goToItem = function (item) {
            $location.path('media/media/edit/' + item.id);
        };
        $scope.add = function () {
            $scope.mediaPickerOverlay = {
                view: 'mediapicker',
                title: 'Select media',
                startNodeId: $scope.model.config.startNodeId,
                startNodeIsVirtual: $scope.model.config.startNodeIsVirtual,
                multiPicker: multiPicker,
                onlyImages: onlyImages,
                disableFolderSelect: disableFolderSelect,
                show: true,
                submit: function (model) {
                    _.each(model.selectedImages, function (media, i) {
                        // if there is no thumbnail, try getting one if the media is not a placeholder item
                        if (!media.thumbnail && media.id && media.metaData) {
                            media.thumbnail = mediaHelper.resolveFileFromEntity(media, true);
                        }
                        $scope.mediaItems.push(media);
                        if ($scope.model.config.idType === 'udi') {
                            $scope.ids.push(media.udi);
                        } else {
                            $scope.ids.push(media.id);
                        }
                    });
                    $scope.sync();
                    $scope.mediaPickerOverlay.show = false;
                    $scope.mediaPickerOverlay = null;
                }
            };
        };
        $scope.sortableOptions = {
            disabled: !$scope.isMultiPicker,
            items: 'li:not(.add-wrapper)',
            cancel: '.unsortable',
            update: function (e, ui) {
                var r = [];
                // TODO: Instead of doing this with a half second delay would be better to use a watch like we do in the
                // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the
                // watch do all the rest.
                $timeout(function () {
                    angular.forEach($scope.mediaItems, function (value, key) {
                        r.push($scope.model.config.idType === 'udi' ? value.udi : value.id);
                    });
                    $scope.ids = r;
                    $scope.sync();
                }, 500, false);
            }
        };
        $scope.sync = function () {
            $scope.model.value = $scope.ids.join();
        };
        $scope.showAdd = function () {
            if (!multiPicker) {
                if ($scope.model.value && $scope.model.value !== '') {
                    return false;
                }
            }
            return true;
        };
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again if it has changed from the server
            setupViewModel();
        };
    });
    //this controller simply tells the dialogs service to open a memberPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function memberGroupPicker($scope, dialogService) {
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        $scope.renderModel = [];
        $scope.allowRemove = true;
        if ($scope.model.value) {
            var modelIds = $scope.model.value.split(',');
            _.each(modelIds, function (item, i) {
                $scope.renderModel.push({
                    name: item,
                    id: item,
                    icon: 'icon-users'
                });
            });
        }
        $scope.openMemberGroupPicker = function () {
            $scope.memberGroupPicker = {};
            $scope.memberGroupPicker.multiPicker = true;
            $scope.memberGroupPicker.view = 'memberGroupPicker';
            $scope.memberGroupPicker.show = true;
            $scope.memberGroupPicker.submit = function (model) {
                if (model.selectedMemberGroups) {
                    _.each(model.selectedMemberGroups, function (item, i) {
                        $scope.add(item);
                    });
                }
                if (model.selectedMemberGroup) {
                    $scope.clear();
                    $scope.add(model.selectedMemberGroup);
                }
                $scope.memberGroupPicker.show = false;
                $scope.memberGroupPicker = null;
            };
            $scope.memberGroupPicker.close = function (oldModel) {
                $scope.memberGroupPicker.show = false;
                $scope.memberGroupPicker = null;
            };
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
        };
        $scope.add = function (item) {
            var currIds = _.map($scope.renderModel, function (i) {
                return i.id;
            });
            if (currIds.indexOf(item) < 0) {
                $scope.renderModel.push({
                    name: item,
                    id: item,
                    icon: 'icon-users'
                });
            }
        };
        $scope.clear = function () {
            $scope.renderModel = [];
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var currIds = _.map($scope.renderModel, function (i) {
                return i.id;
            });
            $scope.model.value = trim(currIds.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupPickerController', memberGroupPicker);
    function memberGroupController($rootScope, $scope, dialogService, mediaResource, imageHelper, $log) {
        //set the available to the keys of the dictionary who's value is true
        $scope.getAvailable = function () {
            var available = [];
            for (var n in $scope.model.value) {
                if ($scope.model.value[n] === false) {
                    available.push(n);
                }
            }
            return available;
        };
        //set the selected to the keys of the dictionary who's value is true
        $scope.getSelected = function () {
            var selected = [];
            for (var n in $scope.model.value) {
                if ($scope.model.value[n] === true) {
                    selected.push(n);
                }
            }
            return selected;
        };
        $scope.addItem = function (item) {
            //keep the model up to date
            $scope.model.value[item] = true;
        };
        $scope.removeItem = function (item) {
            //keep the model up to date
            $scope.model.value[item] = false;
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberGroupController', memberGroupController);
    //this controller simply tells the dialogs service to open a memberPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    function memberPickerController($scope, dialogService, entityResource, $log, iconHelper, angularHelper) {
        function trim(str, chr) {
            var rgxtrim = !chr ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
            return str.replace(rgxtrim, '');
        }
        $scope.renderModel = [];
        $scope.allowRemove = true;
        var dialogOptions = {
            multiPicker: false,
            entityType: 'Member',
            section: 'member',
            treeAlias: 'member',
            filter: function (i) {
                return i.metaData.isContainer == true;
            },
            filterCssClass: 'not-allowed',
            callback: function (data) {
                if (angular.isArray(data)) {
                    _.each(data, function (item, i) {
                        $scope.add(item);
                    });
                } else {
                    $scope.clear();
                    $scope.add(data);
                }
                angularHelper.getCurrentForm($scope).$setDirty();
            }
        };
        //since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
        // pre-value config on to the dialog options
        if ($scope.model.config) {
            angular.extend(dialogOptions, $scope.model.config);
        }
        $scope.openMemberPicker = function () {
            $scope.memberPickerOverlay = dialogOptions;
            $scope.memberPickerOverlay.view = 'memberPicker';
            $scope.memberPickerOverlay.show = true;
            $scope.memberPickerOverlay.submit = function (model) {
                if (model.selection) {
                    _.each(model.selection, function (item, i) {
                        $scope.add(item);
                    });
                }
                $scope.memberPickerOverlay.show = false;
                $scope.memberPickerOverlay = null;
            };
            $scope.memberPickerOverlay.close = function (oldModel) {
                $scope.memberPickerOverlay.show = false;
                $scope.memberPickerOverlay = null;
            };
        };
        $scope.remove = function (index) {
            $scope.renderModel.splice(index, 1);
        };
        $scope.add = function (item) {
            var currIds = _.map($scope.renderModel, function (i) {
                if ($scope.model.config.idType === 'udi') {
                    return i.udi;
                } else {
                    return i.id;
                }
            });
            var itemId = $scope.model.config.idType === 'udi' ? item.udi : item.id;
            if (currIds.indexOf(itemId) < 0) {
                item.icon = iconHelper.convertFromLegacyIcon(item.icon);
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    udi: item.udi,
                    icon: item.icon
                });
            }
        };
        $scope.clear = function () {
            $scope.renderModel = [];
        };
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var currIds = _.map($scope.renderModel, function (i) {
                if ($scope.model.config.idType === 'udi') {
                    return i.udi;
                } else {
                    return i.id;
                }
            });
            $scope.model.value = trim(currIds.join(), ',');
        });
        //when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        //load member data
        var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
        entityResource.getByIds(modelIds, 'Member').then(function (data) {
            _.each(data, function (item, i) {
                // set default icon if it's missing
                item.icon = item.icon ? iconHelper.convertFromLegacyIcon(item.icon) : 'icon-user';
                $scope.renderModel.push({
                    name: item.name,
                    id: item.id,
                    udi: item.udi,
                    icon: item.icon
                });
            });
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MemberPickerController', memberPickerController);
    function MultipleTextBoxController($scope, $timeout) {
        var backspaceHits = 0;
        // Set the visible prompt to -1 to ensure it will not be visible
        $scope.promptIsVisible = '-1';
        $scope.sortableOptions = {
            axis: 'y',
            containment: 'parent',
            cursor: 'move',
            items: '> div.textbox-wrapper',
            tolerance: 'pointer'
        };
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        //add any fields that there isn't values for
        if ($scope.model.config.min > 0) {
            for (var i = 0; i < $scope.model.config.min; i++) {
                if (i + 1 > $scope.model.value.length) {
                    $scope.model.value.push({ value: '' });
                }
            }
        }
        $scope.addRemoveOnKeyDown = function (event, index) {
            var txtBoxValue = $scope.model.value[index];
            event.preventDefault();
            switch (event.keyCode) {
            case 13:
                if ($scope.model.config.max <= 0 && txtBoxValue.value || $scope.model.value.length < $scope.model.config.max && txtBoxValue.value) {
                    var newItemIndex = index + 1;
                    $scope.model.value.splice(newItemIndex, 0, { value: '' });
                    //Focus on the newly added value
                    $scope.model.value[newItemIndex].hasFocus = true;
                }
                break;
            case 8:
                if ($scope.model.value.length > $scope.model.config.min) {
                    var remainder = [];
                    // Used to require an extra hit on backspace for the field to be removed
                    if (txtBoxValue.value === '') {
                        backspaceHits++;
                    } else {
                        backspaceHits = 0;
                    }
                    if (txtBoxValue.value === '' && backspaceHits === 2) {
                        for (var x = 0; x < $scope.model.value.length; x++) {
                            if (x !== index) {
                                remainder.push($scope.model.value[x]);
                            }
                        }
                        $scope.model.value = remainder;
                        var prevItemIndex = index - 1;
                        //Set focus back on false as the directive only watches for true
                        if (prevItemIndex >= 0) {
                            $scope.model.value[prevItemIndex].hasFocus = false;
                            $timeout(function () {
                                //Focus on the previous value
                                $scope.model.value[prevItemIndex].hasFocus = true;
                            });
                        }
                        backspaceHits = 0;
                    }
                }
                break;
            default:
            }
        };
        $scope.add = function () {
            if ($scope.model.config.max <= 0 || $scope.model.value.length < $scope.model.config.max) {
                $scope.model.value.push({ value: '' });
                // focus new value
                var newItemIndex = $scope.model.value.length - 1;
                $scope.model.value[newItemIndex].hasFocus = true;
            }
        };
        $scope.remove = function (index) {
            // Make sure not to trigger other prompts when remove is triggered
            $scope.hidePrompt();
            var remainder = [];
            for (var x = 0; x < $scope.model.value.length; x++) {
                if (x !== index) {
                    remainder.push($scope.model.value[x]);
                }
            }
            $scope.model.value = remainder;
        };
        $scope.showPrompt = function (idx, item) {
            var i = $scope.model.value.indexOf(item);
            // Make the prompt visible for the clicked tag only
            if (i === idx) {
                $scope.promptIsVisible = i;
            }
        };
        $scope.hidePrompt = function () {
            $scope.promptIsVisible = '-1';
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MultipleTextBoxController', MultipleTextBoxController);
    function multiUrlPickerController($scope, angularHelper, localizationService, entityResource, iconHelper) {
        $scope.renderModel = [];
        if ($scope.preview) {
            return;
        }
        if (!Array.isArray($scope.model.value)) {
            $scope.model.value = [];
        }
        var currentForm = angularHelper.getCurrentForm($scope);
        $scope.sortableOptions = {
            distance: 10,
            tolerance: 'pointer',
            scroll: true,
            zIndex: 6000,
            update: function () {
                currentForm.$setDirty();
            }
        };
        $scope.model.value.forEach(function (link) {
            link.icon = iconHelper.convertFromLegacyIcon(link.icon);
            $scope.renderModel.push(link);
        });
        $scope.$on('formSubmitting', function () {
            $scope.model.value = $scope.renderModel;
        });
        $scope.$watch(function () {
            return $scope.renderModel.length;
        }, function () {
            if ($scope.model.config && $scope.model.config.minNumber) {
                $scope.multiUrlPickerForm.minCount.$setValidity('minCount', +$scope.model.config.minNumber <= $scope.renderModel.length);
            }
            if ($scope.model.config && $scope.model.config.maxNumber) {
                $scope.multiUrlPickerForm.maxCount.$setValidity('maxCount', +$scope.model.config.maxNumber >= $scope.renderModel.length);
            }
            $scope.sortableOptions.disabled = $scope.renderModel.length === 1;
        });
        $scope.remove = function ($index) {
            $scope.renderModel.splice($index, 1);
            currentForm.$setDirty();
        };
        $scope.openLinkPicker = function (link, $index) {
            var target = link ? {
                name: link.name,
                anchor: link.queryString,
                // the linkPicker breaks if it get an udi for media
                udi: link.isMedia ? null : link.udi,
                url: link.url,
                target: link.target
            } : null;
            $scope.linkPickerOverlay = {
                view: 'linkpicker',
                currentTarget: target,
                show: true,
                submit: function (model) {
                    if (model.target.url || model.target.anchor) {
                        // if an anchor exists, check that it is appropriately prefixed
                        if (model.target.anchor && model.target.anchor[0] !== '?' && model.target.anchor[0] !== '#') {
                            model.target.anchor = (model.target.anchor.indexOf('=') === -1 ? '#' : '?') + model.target.anchor;
                        }
                        if (link) {
                            if (link.isMedia && link.url === model.target.url) {
                            } else {
                                link.udi = model.target.udi;
                                link.isMedia = model.target.isMedia;
                            }
                            link.name = model.target.name || model.target.url || model.target.anchor;
                            link.queryString = model.target.anchor;
                            link.target = model.target.target;
                            link.url = model.target.url;
                        } else {
                            link = {
                                isMedia: model.target.isMedia,
                                name: model.target.name || model.target.url || model.target.anchor,
                                queryString: model.target.anchor,
                                target: model.target.target,
                                udi: model.target.udi,
                                url: model.target.url
                            };
                            $scope.renderModel.push(link);
                        }
                        if (link.udi) {
                            var entityType = link.isMedia ? 'media' : 'document';
                            entityResource.getById(link.udi, entityType).then(function (data) {
                                link.icon = iconHelper.convertFromLegacyIcon(data.icon);
                                link.published = data.metaData && data.metaData.IsPublished === false && entityType === 'Document' ? false : true;
                                link.trashed = data.trashed;
                                if (link.trashed) {
                                    item.url = localizationService.dictionary.general_recycleBin;
                                }
                            });
                        } else {
                            link.icon = 'icon-link';
                            link.published = true;
                        }
                        currentForm.$setDirty();
                    }
                    $scope.linkPickerOverlay.show = false;
                    $scope.linkPickerOverlay = null;
                }
            };
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.MultiUrlPickerController', multiUrlPickerController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.DocTypePickerController', [
        '$scope',
        'Umbraco.PropertyEditors.NestedContent.Resources',
        function ($scope, ncResources) {
            $scope.add = function () {
                $scope.model.value.push({
                    // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                    // For good measure we'll also prefix the tab alias "nc"
                    ncAlias: '',
                    ncTabAlias: '',
                    nameTemplate: ''
                });
            };
            $scope.remove = function (index) {
                $scope.model.value.splice(index, 1);
            };
            $scope.sortableOptions = {
                axis: 'y',
                cursor: 'move',
                handle: '.icon-navigation'
            };
            $scope.docTypeTabs = {};
            ncResources.getContentTypes().then(function (docTypes) {
                $scope.model.docTypes = docTypes;
                // Populate document type tab dictionary
                docTypes.forEach(function (value) {
                    $scope.docTypeTabs[value.alias] = value.tabs;
                });
            });
            $scope.selectableDocTypesFor = function (config) {
                // return all doctypes that are:
                // 1. either already selected for this config, or
                // 2. not selected in any other config
                return _.filter($scope.model.docTypes, function (docType) {
                    return docType.alias === config.ncAlias || !_.find($scope.model.value, function (c) {
                        return docType.alias === c.ncAlias;
                    });
                });
            };
            if (!$scope.model.value) {
                $scope.model.value = [];
                $scope.add();
            }
        }
    ]);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.NestedContent.PropertyEditorController', [
        '$scope',
        '$interpolate',
        '$filter',
        '$timeout',
        'contentResource',
        'localizationService',
        'iconHelper',
        function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {
            //$scope.model.config.contentTypes;
            //$scope.model.config.minItems;
            //$scope.model.config.maxItems;
            //console.log($scope);
            var inited = false;
            _.each($scope.model.config.contentTypes, function (contentType) {
                contentType.nameExp = !!contentType.nameTemplate ? $interpolate(contentType.nameTemplate) : undefined;
            });
            $scope.editIconTitle = '';
            $scope.moveIconTitle = '';
            $scope.deleteIconTitle = '';
            // localize the edit icon title
            localizationService.localize('general_edit').then(function (value) {
                $scope.editIconTitle = value;
            });
            // localize the delete icon title
            localizationService.localize('general_delete').then(function (value) {
                $scope.deleteIconTitle = value;
            });
            // localize the move icon title
            localizationService.localize('actions_move').then(function (value) {
                $scope.moveIconTitle = value;
            });
            $scope.nodes = [];
            $scope.currentNode = undefined;
            $scope.realCurrentNode = undefined;
            $scope.scaffolds = undefined;
            $scope.sorting = false;
            $scope.minItems = $scope.model.config.minItems || 0;
            $scope.maxItems = $scope.model.config.maxItems || 0;
            if ($scope.maxItems == 0)
                $scope.maxItems = 1000;
            $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
            $scope.showIcons = $scope.model.config.showIcons || true;
            $scope.wideMode = $scope.model.config.hideLabel == '1';
            // helper to force the current form into the dirty state
            $scope.setDirty = function () {
                if ($scope.propertyForm) {
                    $scope.propertyForm.$setDirty();
                }
            };
            $scope.addNode = function (alias) {
                var scaffold = $scope.getScaffold(alias);
                var newNode = initNode(scaffold, null);
                $scope.currentNode = newNode;
                $scope.setDirty();
            };
            $scope.openNodeTypePicker = function ($event) {
                if ($scope.nodes.length >= $scope.maxItems) {
                    return;
                }
                $scope.overlayMenu = {
                    title: localizationService.localize('grid_insertControl'),
                    show: false,
                    style: {},
                    filter: $scope.scaffolds.length > 15 ? true : false,
                    view: 'itempicker',
                    event: $event,
                    submit: function (model) {
                        if (model && model.selectedItem) {
                            $scope.addNode(model.selectedItem.alias);
                        }
                        $scope.overlayMenu.show = false;
                        $scope.overlayMenu = null;
                    },
                    close: function () {
                        $scope.overlayMenu.show = false;
                        $scope.overlayMenu = null;
                    }
                };
                // this could be used for future limiting on node types
                $scope.overlayMenu.availableItems = [];
                _.each($scope.scaffolds, function (scaffold) {
                    $scope.overlayMenu.availableItems.push({
                        alias: scaffold.contentTypeAlias,
                        name: scaffold.contentTypeName,
                        icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
                    });
                });
                if ($scope.overlayMenu.availableItems.length === 0) {
                    return;
                }
                if ($scope.overlayMenu.availableItems.length === 1) {
                    // only one scaffold type - no need to display the picker
                    $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                    return;
                }
                $scope.overlayMenu.show = true;
            };
            $scope.editNode = function (idx) {
                if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
                    $scope.currentNode = undefined;
                } else {
                    $scope.currentNode = $scope.nodes[idx];
                }
            };
            $scope.deleteNode = function (idx) {
                if ($scope.nodes.length > $scope.model.config.minItems) {
                    if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                        localizationService.localize('content_nestedContentDeleteItem').then(function (value) {
                            if (confirm(value)) {
                                $scope.nodes.splice(idx, 1);
                                $scope.setDirty();
                                updateModel();
                            }
                        });
                    } else {
                        $scope.nodes.splice(idx, 1);
                        $scope.setDirty();
                        updateModel();
                    }
                }
            };
            $scope.getName = function (idx) {
                var name = 'Item ' + (idx + 1);
                if ($scope.model.value[idx]) {
                    var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);
                    if (contentType != null && contentType.nameExp) {
                        // Run the expression against the stored dictionary value, NOT the node object
                        var item = $scope.model.value[idx];
                        // Add a temporary index property
                        item['$index'] = idx + 1;
                        var newName = contentType.nameExp(item);
                        if (newName && (newName = $.trim(newName))) {
                            name = newName;
                        }
                        // Delete the index property as we don't want to persist it
                        delete item['$index'];
                    }
                }
                // Update the nodes actual name value
                if ($scope.nodes[idx].name !== name) {
                    $scope.nodes[idx].name = name;
                }
                return name;
            };
            $scope.getIcon = function (idx) {
                var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
                return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : 'icon-folder';
            };
            $scope.sortableOptions = {
                axis: 'y',
                cursor: 'move',
                handle: '.umb-nested-content__icon--move',
                start: function (ev, ui) {
                    updateModel();
                    // Yea, yea, we shouldn't modify the dom, sue me
                    $('#umb-nested-content--' + $scope.model.id + ' .umb-rte textarea').each(function () {
                        tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                        $(this).css('visibility', 'hidden');
                    });
                    $scope.$apply(function () {
                        $scope.sorting = true;
                    });
                },
                update: function (ev, ui) {
                    $scope.setDirty();
                },
                stop: function (ev, ui) {
                    $('#umb-nested-content--' + $scope.model.id + ' .umb-rte textarea').each(function () {
                        tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                        $(this).css('visibility', 'visible');
                    });
                    $scope.$apply(function () {
                        $scope.sorting = false;
                        updateModel();
                    });
                }
            };
            $scope.getScaffold = function (alias) {
                return _.find($scope.scaffolds, function (scaffold) {
                    return scaffold.contentTypeAlias == alias;
                });
            };
            $scope.getContentTypeConfig = function (alias) {
                return _.find($scope.model.config.contentTypes, function (contentType) {
                    return contentType.ncAlias == alias;
                });
            };
            var notSupported = [
                'Umbraco.Tags',
                'Umbraco.UploadField',
                'Umbraco.ImageCropper'
            ];
            // Initialize
            var scaffoldsLoaded = 0;
            $scope.scaffolds = [];
            _.each($scope.model.config.contentTypes, function (contentType) {
                contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
                    // remove all tabs except the specified tab
                    var tab = _.find(scaffold.tabs, function (tab) {
                        return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == '');
                    });
                    scaffold.tabs = [];
                    if (tab) {
                        scaffold.tabs.push(tab);
                        angular.forEach(tab.properties, function (property) {
                            if (_.find(notSupported, function (x) {
                                    return x === property.editor;
                                })) {
                                property.notSupported = true;
                                //TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                                property.notSupportedMessage = 'Property ' + property.label + ' uses editor ' + property.editor + ' which is not supported by Nested Content.';
                            }
                        });
                    }
                    // Store the scaffold object
                    $scope.scaffolds.push(scaffold);
                    scaffoldsLoaded++;
                    initIfAllScaffoldsHaveLoaded();
                }, function (error) {
                    scaffoldsLoaded++;
                    initIfAllScaffoldsHaveLoaded();
                });
            });
            var initIfAllScaffoldsHaveLoaded = function () {
                // Initialize when all scaffolds have loaded
                if ($scope.model.config.contentTypes.length == scaffoldsLoaded) {
                    // Because we're loading the scaffolds async one at a time, we need to
                    // sort them explicitly according to the sort order defined by the data type.
                    var contentTypeAliases = [];
                    _.each($scope.model.config.contentTypes, function (contentType) {
                        contentTypeAliases.push(contentType.ncAlias);
                    });
                    $scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) {
                        return contentTypeAliases.indexOf(s.contentTypeAlias);
                    });
                    // Convert stored nodes
                    if ($scope.model.value) {
                        for (var i = 0; i < $scope.model.value.length; i++) {
                            var item = $scope.model.value[i];
                            var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
                            if (scaffold == null) {
                                // No such scaffold - the content type might have been deleted. We need to skip it.
                                continue;
                            }
                            initNode(scaffold, item);
                        }
                    }
                    // Enforce min items
                    if ($scope.nodes.length < $scope.model.config.minItems) {
                        for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                            $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                        }
                    }
                    // If there is only one item, set it as current node
                    if ($scope.singleMode || $scope.nodes.length == 1 && $scope.maxItems == 1) {
                        $scope.currentNode = $scope.nodes[0];
                    }
                    inited = true;
                }
            };
            var initNode = function (scaffold, item) {
                var node = angular.copy(scaffold);
                node.key = item && item.key ? item.key : UUID.generate();
                node.ncContentTypeAlias = scaffold.contentTypeAlias;
                for (var t = 0; t < node.tabs.length; t++) {
                    var tab = node.tabs[t];
                    for (var p = 0; p < tab.properties.length; p++) {
                        var prop = tab.properties[p];
                        prop.propertyAlias = prop.alias;
                        prop.alias = $scope.model.alias + '___' + prop.alias;
                        // Force validation to occur server side as this is the
                        // only way we can have consistancy between mandatory and
                        // regex validation messages. Not ideal, but it works.
                        prop.validation = {
                            mandatory: false,
                            pattern: ''
                        };
                        if (item) {
                            if (item[prop.propertyAlias]) {
                                prop.value = item[prop.propertyAlias];
                            }
                        }
                    }
                }
                $scope.nodes.push(node);
                return node;
            };
            var updateModel = function () {
                if ($scope.realCurrentNode) {
                    $scope.$broadcast('ncSyncVal', { key: $scope.realCurrentNode.key });
                }
                if (inited) {
                    var newValues = [];
                    for (var i = 0; i < $scope.nodes.length; i++) {
                        var node = $scope.nodes[i];
                        var newValue = {
                            key: node.key,
                            name: node.name,
                            ncContentTypeAlias: node.ncContentTypeAlias
                        };
                        for (var t = 0; t < node.tabs.length; t++) {
                            var tab = node.tabs[t];
                            for (var p = 0; p < tab.properties.length; p++) {
                                var prop = tab.properties[p];
                                if (typeof prop.value !== 'function') {
                                    newValue[prop.propertyAlias] = prop.value;
                                }
                            }
                        }
                        newValues.push(newValue);
                    }
                    $scope.model.value = newValues;
                }
            };
            $scope.$watch('currentNode', function (newVal) {
                updateModel();
                $scope.realCurrentNode = newVal;
            });
            var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
                updateModel();
            });
            $scope.$on('$destroy', function () {
                unsubscribe();
            });
            //TODO: Move this into a shared location?
            var UUID = function () {
                var self = {};
                var lut = [];
                for (var i = 0; i < 256; i++) {
                    lut[i] = (i < 16 ? '0' : '') + i.toString(16);
                }
                self.generate = function () {
                    var d0 = Math.random() * 4294967295 | 0;
                    var d1 = Math.random() * 4294967295 | 0;
                    var d2 = Math.random() * 4294967295 | 0;
                    var d3 = Math.random() * 4294967295 | 0;
                    return lut[d0 & 255] + lut[d0 >> 8 & 255] + lut[d0 >> 16 & 255] + lut[d0 >> 24 & 255] + '-' + lut[d1 & 255] + lut[d1 >> 8 & 255] + '-' + lut[d1 >> 16 & 15 | 64] + lut[d1 >> 24 & 255] + '-' + lut[d2 & 63 | 128] + lut[d2 >> 8 & 255] + '-' + lut[d2 >> 16 & 255] + lut[d2 >> 24 & 255] + lut[d3 & 255] + lut[d3 >> 8 & 255] + lut[d3 >> 16 & 255] + lut[d3 >> 24 & 255];
                };
                return self;
            }();
        }
    ]);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.RadioButtonsController', function ($scope) {
        if (angular.isObject($scope.model.config.items)) {
            //now we need to format the items in the dictionary because we always want to have an array
            var newItems = [];
            var vals = _.values($scope.model.config.items);
            var keys = _.keys($scope.model.config.items);
            for (var i = 0; i < vals.length; i++) {
                newItems.push({
                    id: keys[i],
                    sortOrder: vals[i].sortOrder,
                    value: vals[i].value
                });
            }
            //ensure the items are sorted by the provided sort order
            newItems.sort(function (a, b) {
                return a.sortOrder > b.sortOrder ? 1 : b.sortOrder > a.sortOrder ? -1 : 0;
            });
            //re-assign
            $scope.model.config.items = newItems;
        }
    });
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.ReadOnlyValueController
 * @function
 * 
 * @description
 * The controller for the readonlyvalue property editor. 
 *  This controller offer more functionality than just a simple label as it will be able to apply formatting to the 
 *  value to be displayed. This means that we also have to apply more complex logic of watching the model value when 
 *  it changes because we are creating a new scope value called displayvalue which will never change based on the server data.
 *  In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of 
 *  readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here.
*/
    function ReadOnlyValueController($rootScope, $scope, $filter) {
        function formatDisplayValue() {
            if ($scope.model.config && angular.isArray($scope.model.config) && $scope.model.config.length > 0 && $scope.model.config[0] && $scope.model.config.filter) {
                if ($scope.model.config.format) {
                    $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format);
                } else {
                    $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value);
                }
            } else {
                $scope.displayvalue = $scope.model.value;
            }
        }
        //format the display value on init:
        formatDisplayValue();
        $scope.$watch('model.value', function (newVal, oldVal) {
            //cannot just check for !newVal because it might be an empty string which we 
            //want to look for.
            if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
                //update the display val again
                formatDisplayValue();
            }
        });
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.ReadOnlyValueController', ReadOnlyValueController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.RelatedLinksController', function ($rootScope, $scope, dialogService, iconHelper) {
        if (!$scope.model.value) {
            $scope.model.value = [];
        }
        $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE;
        $scope.newCaption = '';
        $scope.newLink = 'http://';
        $scope.newNewWindow = false;
        $scope.newInternal = null;
        $scope.newInternalName = '';
        $scope.newInternalIcon = null;
        $scope.addExternal = true;
        $scope.currentEditLink = null;
        $scope.hasError = false;
        $scope.internal = function ($event) {
            $scope.currentEditLink = null;
            $scope.contentPickerOverlay = {};
            $scope.contentPickerOverlay.view = 'contentpicker';
            $scope.contentPickerOverlay.multiPicker = false;
            $scope.contentPickerOverlay.show = true;
            $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : 'int';
            $scope.contentPickerOverlay.submit = function (model) {
                select(model.selection[0]);
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
            $scope.contentPickerOverlay.close = function (oldModel) {
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
            $event.preventDefault();
        };
        $scope.selectInternal = function ($event, link) {
            $scope.currentEditLink = link;
            $scope.contentPickerOverlay = {};
            $scope.contentPickerOverlay.view = 'contentpicker';
            $scope.contentPickerOverlay.multiPicker = false;
            $scope.contentPickerOverlay.show = true;
            $scope.contentPickerOverlay.idType = $scope.model.config.idType ? $scope.model.config.idType : 'int';
            $scope.contentPickerOverlay.submit = function (model) {
                select(model.selection[0]);
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
            $scope.contentPickerOverlay.close = function (oldModel) {
                $scope.contentPickerOverlay.show = false;
                $scope.contentPickerOverlay = null;
            };
            $event.preventDefault();
        };
        $scope.edit = function (idx) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                $scope.model.value[i].edit = false;
            }
            $scope.model.value[idx].edit = true;
        };
        $scope.saveEdit = function (idx) {
            $scope.model.value[idx].title = $scope.model.value[idx].caption;
            $scope.model.value[idx].edit = false;
        };
        $scope.delete = function (idx) {
            $scope.model.value.splice(idx, 1);
        };
        $scope.add = function ($event) {
            if (!angular.isArray($scope.model.value)) {
                $scope.model.value = [];
            }
            if ($scope.newCaption == '') {
                $scope.hasError = true;
            } else {
                if ($scope.addExternal) {
                    var newExtLink = new function () {
                        this.caption = $scope.newCaption;
                        this.link = $scope.newLink;
                        this.newWindow = $scope.newNewWindow;
                        this.edit = false;
                        this.isInternal = false;
                        this.type = 'external';
                        this.title = $scope.newCaption;
                    }();
                    $scope.model.value.push(newExtLink);
                } else {
                    var newIntLink = new function () {
                        this.caption = $scope.newCaption;
                        this.link = $scope.newInternal;
                        this.newWindow = $scope.newNewWindow;
                        this.internal = $scope.newInternal;
                        this.edit = false;
                        this.isInternal = true;
                        this.internalName = $scope.newInternalName;
                        this.internalIcon = $scope.newInternalIcon;
                        this.type = 'internal';
                        this.title = $scope.newCaption;
                    }();
                    $scope.model.value.push(newIntLink);
                }
                $scope.newCaption = '';
                $scope.newLink = 'http://';
                $scope.newNewWindow = false;
                $scope.newInternal = null;
                $scope.newInternalName = '';
                $scope.newInternalIcon = null;
            }
            $event.preventDefault();
        };
        $scope.switch = function ($event) {
            $scope.addExternal = !$scope.addExternal;
            $event.preventDefault();
        };
        $scope.switchLinkType = function ($event, link) {
            link.isInternal = !link.isInternal;
            link.type = link.isInternal ? 'internal' : 'external';
            if (!link.isInternal)
                link.link = $scope.newLink;
            $event.preventDefault();
        };
        $scope.move = function (index, direction) {
            var temp = $scope.model.value[index];
            $scope.model.value[index] = $scope.model.value[index + direction];
            $scope.model.value[index + direction] = temp;
        };
        //helper for determining if a user can add items
        $scope.canAdd = function () {
            return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible();
        };
        //helper that returns if an item can be sorted
        $scope.canSort = function () {
            return countVisible() > 1;
        };
        $scope.sortableOptions = {
            axis: 'y',
            handle: '.handle',
            cursor: 'move',
            cancel: '.no-drag',
            containment: 'parent',
            placeholder: 'sortable-placeholder',
            forcePlaceholderSize: true,
            helper: function (e, ui) {
                // When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
                ui.children().each(function () {
                    $(this).width($(this).width());
                });
                return ui;
            },
            items: '> tr:not(.unsortable)',
            tolerance: 'pointer',
            update: function (e, ui) {
                // Get the new and old index for the moved element (using the URL as the identifier)
                var newIndex = ui.item.index();
                var movedLinkUrl = ui.item.attr('data-link');
                var originalIndex = getElementIndexByUrl(movedLinkUrl);
                // Move the element in the model
                var movedElement = $scope.model.value[originalIndex];
                $scope.model.value.splice(originalIndex, 1);
                $scope.model.value.splice(newIndex, 0, movedElement);
            },
            start: function (e, ui) {
                //ui.placeholder.html("<td colspan='5'></td>");
                // Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
                var cellCount = 0;
                $('td, th', ui.helper).each(function () {
                    // For each td or th try and get it's colspan attribute, and add that or 1 to the total
                    var colspan = 1;
                    var colspanAttr = $(this).attr('colspan');
                    if (colspanAttr > 1) {
                        colspan = colspanAttr;
                    }
                    cellCount += colspan;
                });
                // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
                ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(ui.item.height());
            }
        };
        //helper to count what is visible
        function countVisible() {
            return $scope.model.value.length;
        }
        function isNumeric(n) {
            return !isNaN(parseFloat(n)) && isFinite(n);
        }
        function getElementIndexByUrl(url) {
            for (var i = 0; i < $scope.model.value.length; i++) {
                if ($scope.model.value[i].link == url) {
                    return i;
                }
            }
            return -1;
        }
        function select(data) {
            if ($scope.currentEditLink != null) {
                $scope.currentEditLink.internal = $scope.model.config.idType === 'udi' ? data.udi : data.id;
                $scope.currentEditLink.internalName = data.name;
                $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon);
                $scope.currentEditLink.link = $scope.model.config.idType === 'udi' ? data.udi : data.id;
            } else {
                $scope.newInternal = $scope.model.config.idType === 'udi' ? data.udi : data.id;
                $scope.newInternalName = data.name;
                $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon);
            }
        }
    });
    angular.module('umbraco').controller('Umbraco.PropertyEditors.RTEController', function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) {
        $scope.isLoading = true;
        //To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
        // because now we have to support having 2x (maybe more at some stage) content editors being displayed at once. This is because
        // we have this mini content editor panel that can be launched with MNTP.
        var d = new Date();
        var n = d.getTime();
        $scope.textAreaHtmlId = $scope.model.alias + '_' + n + '_rte';
        var alreadyDirty = false;
        function syncContent(editor) {
            editor.save();
            angularHelper.safeApply($scope, function () {
                $scope.model.value = editor.getContent();
            });
            if (!alreadyDirty) {
                //make the form dirty manually so that the track changes works, setting our model doesn't trigger
                // the angular bits because tinymce replaces the textarea.
                var currForm = angularHelper.getCurrentForm($scope);
                currForm.$setDirty();
                alreadyDirty = true;
            }
        }
        tinyMceService.configuration().then(function (tinyMceConfig) {
            //config value from general tinymce.config file
            var validElements = tinyMceConfig.validElements;
            //These are absolutely required in order for the macros to render inline
            //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
            var extendedValidElements = '@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]';
            var invalidElements = tinyMceConfig.inValidElements;
            var plugins = _.map(tinyMceConfig.plugins, function (plugin) {
                if (plugin.useOnFrontend) {
                    return plugin.name;
                }
            }).join(' ');
            var editorConfig = $scope.model.config.editor;
            if (!editorConfig || angular.isString(editorConfig)) {
                editorConfig = tinyMceService.defaultPrevalues();
            }
            //config value on the data type
            var toolbar = editorConfig.toolbar.join(' | ');
            var stylesheets = [];
            var styleFormats = [];
            var await = [];
            if (!editorConfig.maxImageSize && editorConfig.maxImageSize != 0) {
                editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
            }
            //queue file loading
            if (typeof tinymce === 'undefined') {
                // Don't reload tinymce if already loaded
                await.push(assetsService.loadJs('lib/tinymce/tinymce.min.js', $scope));
            }
            //queue rules loading
            angular.forEach(editorConfig.stylesheets, function (val, key) {
                stylesheets.push(Umbraco.Sys.ServerVariables.umbracoSettings.cssPath + '/' + val + '.css?' + new Date().getTime());
                await.push(stylesheetResource.getRulesByName(val).then(function (rules) {
                    angular.forEach(rules, function (rule) {
                        var r = {};
                        r.title = rule.name;
                        if (rule.selector[0] == '.') {
                            r.inline = 'span';
                            r.classes = rule.selector.substring(1);
                        } else if (rule.selector[0] == '#') {
                            r.inline = 'span';
                            r.attributes = { id: rule.selector.substring(1) };
                        } else if (rule.selector[0] != '.' && rule.selector.indexOf('.') > -1) {
                            var split = rule.selector.split('.');
                            r.block = split[0];
                            r.classes = rule.selector.substring(rule.selector.indexOf('.') + 1).replace('.', ' ');
                        } else if (rule.selector[0] != '#' && rule.selector.indexOf('#') > -1) {
                            var split = rule.selector.split('#');
                            r.block = split[0];
                            r.classes = rule.selector.substring(rule.selector.indexOf('#') + 1);
                        } else {
                            r.block = rule.selector;
                        }
                        styleFormats.push(r);
                    });
                }));
            });
            //stores a reference to the editor
            var tinyMceEditor = null;
            // these languages are available for localization
            var availableLanguages = [
                'da',
                'de',
                'en',
                'en_us',
                'fi',
                'fr',
                'he',
                'it',
                'ja',
                'nl',
                'no',
                'pl',
                'pt',
                'ru',
                'sv',
                'zh'
            ];
            //define fallback language
            var language = 'en_us';
            //get locale from angular and match tinymce format. Angular localization is always in the format of ru-ru, de-de, en-gb, etc.
            //wheras tinymce is in the format of ru, de, en, en_us, etc.
            var localeId = $locale.id.replace('-', '_');
            //try matching the language using full locale format
            var languageMatch = _.find(availableLanguages, function (o) {
                return o === localeId;
            });
            //if no matches, try matching using only the language
            if (languageMatch === undefined) {
                var localeParts = localeId.split('_');
                languageMatch = _.find(availableLanguages, function (o) {
                    return o === localeParts[0];
                });
            }
            //if a match was found - set the language
            if (languageMatch !== undefined) {
                language = languageMatch;
            }
            //wait for queue to end
            $q.all(await).then(function () {
                //create a baseline Config to exten upon
                var baseLineConfigObj = {
                    mode: 'exact',
                    skin: 'umbraco',
                    plugins: plugins,
                    valid_elements: validElements,
                    invalid_elements: invalidElements,
                    extended_valid_elements: extendedValidElements,
                    menubar: false,
                    statusbar: false,
                    relative_urls: false,
                    height: editorConfig.dimensions.height,
                    width: editorConfig.dimensions.width,
                    maxImageSize: editorConfig.maxImageSize,
                    toolbar: toolbar,
                    content_css: stylesheets,
                    style_formats: styleFormats,
                    language: language,
                    //see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix
                    cache_suffix: '?umb__rnd=' + Umbraco.Sys.ServerVariables.application.cacheBuster
                };
                if (tinyMceConfig.customConfig) {
                    //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to
                    // convert it to json instead of having it as a string since this is what tinymce requires
                    for (var i in tinyMceConfig.customConfig) {
                        var val = tinyMceConfig.customConfig[i];
                        if (val) {
                            val = val.toString().trim();
                            if (val.detectIsJson()) {
                                try {
                                    tinyMceConfig.customConfig[i] = JSON.parse(val);
                                    //now we need to check if this custom config key is defined in our baseline, if it is we don't want to
                                    //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise
                                    //if it's an object it will overwrite the baseline
                                    if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) {
                                        //concat it and below this concat'd array will overwrite the baseline in angular.extend
                                        tinyMceConfig.customConfig[i] = baseLineConfigObj[i].concat(tinyMceConfig.customConfig[i]);
                                    }
                                } catch (e) {
                                }
                            }
                            if (val === 'true') {
                                tinyMceConfig.customConfig[i] = true;
                            }
                            if (val === 'false') {
                                tinyMceConfig.customConfig[i] = false;
                            }
                        }
                    }
                    angular.extend(baseLineConfigObj, tinyMceConfig.customConfig);
                }
                //set all the things that user configs should not be able to override
                baseLineConfigObj.elements = $scope.textAreaHtmlId;
                //this is the exact textarea id to replace!
                baseLineConfigObj.setup = function (editor) {
                    //set the reference
                    tinyMceEditor = editor;
                    //enable browser based spell checking
                    editor.on('init', function (e) {
                        editor.getBody().setAttribute('spellcheck', true);
                    });
                    //We need to listen on multiple things here because of the nature of tinymce, it doesn't
                    //fire events when you think!
                    //The change event doesn't fire when content changes, only when cursor points are changed and undo points
                    //are created. the blur event doesn't fire if you insert content into the editor with a button and then
                    //press save.
                    //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can
                    //listen to both change and blur and also on our own 'saving' event. I think this will be best because a
                    //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked
                    //save before the timeout elapsed.
                    //TODO: We need to re-enable something like this to ensure the track changes is working with tinymce
                    // so we can detect if the form is dirty or not, Per has some better events to use as this one triggers
                    // even if you just enter/exit with mouse cursuor which doesn't really mean it's changed.
                    // see: http://issues.umbraco.org/issue/U4-4485
                    //var alreadyDirty = false;
                    //editor.on('change', function (e) {
                    //    angularHelper.safeApply($scope, function () {
                    //        $scope.model.value = editor.getContent();
                    //        if (!alreadyDirty) {
                    //            //make the form dirty manually so that the track changes works, setting our model doesn't trigger
                    //            // the angular bits because tinymce replaces the textarea.
                    //            var currForm = angularHelper.getCurrentForm($scope);
                    //            currForm.$setDirty();
                    //            alreadyDirty = true;
                    //        }
                    //    });
                    //});
                    //when we leave the editor (maybe)
                    editor.on('blur', function (e) {
                        editor.save();
                        angularHelper.safeApply($scope, function () {
                            $scope.model.value = editor.getContent();
                        });
                    });
                    //when buttons modify content
                    editor.on('ExecCommand', function (e) {
                        syncContent(editor);
                    });
                    // Update model on keypress
                    editor.on('KeyUp', function (e) {
                        syncContent(editor);
                    });
                    // Update model on change, i.e. copy/pasted text, plugins altering content
                    editor.on('SetContent', function (e) {
                        if (!e.initial) {
                            syncContent(editor);
                        }
                    });
                    editor.on('ObjectResized', function (e) {
                        var qs = '?width=' + e.width + '&height=' + e.height + '&mode=max';
                        var srcAttr = $(e.target).attr('src');
                        var path = srcAttr.split('?')[0];
                        $(e.target).attr('data-mce-src', path + qs);
                        syncContent(editor);
                    });
                    tinyMceService.createLinkPicker(editor, $scope, function (currentTarget, anchorElement) {
                        $scope.linkPickerOverlay = {
                            view: 'linkpicker',
                            currentTarget: currentTarget,
                            anchors: editorState.current ? tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)) : [],
                            show: true,
                            submit: function (model) {
                                tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);
                                $scope.linkPickerOverlay.show = false;
                                $scope.linkPickerOverlay = null;
                            }
                        };
                    });
                    //Create the insert media plugin
                    tinyMceService.createMediaPicker(editor, $scope, function (currentTarget, userData) {
                        $scope.mediaPickerOverlay = {
                            currentTarget: currentTarget,
                            onlyImages: true,
                            showDetails: true,
                            disableFolderSelect: true,
                            startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
                            startNodeIsVirtual: userData.startMediaIds.length !== 1,
                            view: 'mediapicker',
                            show: true,
                            submit: function (model) {
                                tinyMceService.insertMediaInEditor(editor, model.selectedImages[0]);
                                $scope.mediaPickerOverlay.show = false;
                                $scope.mediaPickerOverlay = null;
                            }
                        };
                    });
                    //Create the embedded plugin
                    tinyMceService.createInsertEmbeddedMedia(editor, $scope, function () {
                        $scope.embedOverlay = {
                            view: 'embed',
                            show: true,
                            submit: function (model) {
                                tinyMceService.insertEmbeddedMediaInEditor(editor, model.embed.preview);
                                $scope.embedOverlay.show = false;
                                $scope.embedOverlay = null;
                            }
                        };
                    });
                    //Create the insert macro plugin
                    tinyMceService.createInsertMacro(editor, $scope, function (dialogData) {
                        $scope.macroPickerOverlay = {
                            view: 'macropicker',
                            dialogData: dialogData,
                            show: true,
                            submit: function (model) {
                                var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, dialogData.renderingEngine);
                                tinyMceService.insertMacroInEditor(editor, macroObject, $scope);
                                $scope.macroPickerOverlay.show = false;
                                $scope.macroPickerOverlay = null;
                            }
                        };
                    });
                };
                /** Loads in the editor */
                function loadTinyMce() {
                    //we need to add a timeout here, to force a redraw so TinyMCE can find
                    //the elements needed
                    $timeout(function () {
                        tinymce.DOM.events.domLoaded = true;
                        tinymce.init(baseLineConfigObj);
                        $scope.isLoading = false;
                    }, 200, false);
                }
                loadTinyMce();
                //here we declare a special method which will be called whenever the value has changed from the server
                //this is instead of doing a watch on the model.value = faster
                $scope.model.onValueChanged = function (newVal, oldVal) {
                    //update the display val again if it has changed from the server;
                    //uses an empty string in the editor when the value is null
                    tinyMceEditor.setContent(newVal || '', { format: 'raw' });
                    //we need to manually fire this event since it is only ever fired based on loading from the DOM, this
                    // is required for our plugins listening to this event to execute
                    tinyMceEditor.fire('LoadContent', null);
                };
                //listen for formSubmitting event (the result is callback used to remove the event subscription)
                var unsubscribe = $scope.$on('formSubmitting', function () {
                    //TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
                    // we do parse it out on the server side but would be nice to do that on the client side before as well.
                    if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {
                        $scope.model.value = tinyMceEditor.getContent();
                    }
                });
                //when the element is disposed we need to unsubscribe!
                // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
                // element might still be there even after the modal has been hidden.
                $scope.$on('$destroy', function () {
                    unsubscribe();
                    if (tinyMceEditor !== undefined && tinyMceEditor != null) {
                        tinyMceEditor.destroy();
                    }
                });
            });
        });
    });
    angular.module('umbraco').controller('Umbraco.PrevalueEditors.RteController', function ($scope, $timeout, $log, tinyMceService, stylesheetResource, assetsService) {
        var cfg = tinyMceService.defaultPrevalues();
        if ($scope.model.value) {
            if (angular.isString($scope.model.value)) {
                $scope.model.value = cfg;
            }
        } else {
            $scope.model.value = cfg;
        }
        if (!$scope.model.value.stylesheets) {
            $scope.model.value.stylesheets = [];
        }
        if (!$scope.model.value.toolbar) {
            $scope.model.value.toolbar = [];
        }
        if (!$scope.model.value.maxImageSize && $scope.model.value.maxImageSize != 0) {
            $scope.model.value.maxImageSize = cfg.maxImageSize;
        }
        tinyMceService.configuration().then(function (config) {
            $scope.tinyMceConfig = config;
            // extend commands with properties for font-icon and if it is a custom command
            $scope.tinyMceConfig.commands = _.map($scope.tinyMceConfig.commands, function (obj) {
                var icon = getFontIcon(obj.frontEndCommand);
                return angular.extend(obj, {
                    fontIcon: icon.name,
                    isCustom: icon.isCustom
                });
            });
        });
        stylesheetResource.getAll().then(function (stylesheets) {
            $scope.stylesheets = stylesheets;
        });
        $scope.selected = function (cmd, alias, lookup) {
            if (lookup && angular.isArray(lookup)) {
                cmd.selected = lookup.indexOf(alias) >= 0;
                return cmd.selected;
            }
            return false;
        };
        $scope.selectCommand = function (command) {
            var index = $scope.model.value.toolbar.indexOf(command.frontEndCommand);
            if (command.selected && index === -1) {
                $scope.model.value.toolbar.push(command.frontEndCommand);
            } else if (index >= 0) {
                $scope.model.value.toolbar.splice(index, 1);
            }
        };
        $scope.selectStylesheet = function (css) {
            var index = $scope.model.value.stylesheets.indexOf(css.name);
            if (css.selected && index === -1) {
                $scope.model.value.stylesheets.push(css.name);
            } else if (index >= 0) {
                $scope.model.value.stylesheets.splice(index, 1);
            }
        };
        // map properties for specific commands
        function getFontIcon(alias) {
            var icon = {
                name: alias,
                isCustom: false
            };
            switch (alias) {
            case 'codemirror':
                icon.name = 'code';
                icon.isCustom = false;
                break;
            case 'styleselect':
            case 'fontsizeselect':
                icon.name = 'icon-list';
                icon.isCustom = true;
                break;
            case 'umbembeddialog':
                icon.name = 'icon-tv';
                icon.isCustom = true;
                break;
            case 'umbmediapicker':
                icon.name = 'icon-picture';
                icon.isCustom = true;
                break;
            case 'umbmacro':
                icon.name = 'icon-settings-alt';
                icon.isCustom = true;
                break;
            case 'umbmacro':
                icon.name = 'icon-settings-alt';
                icon.isCustom = true;
                break;
            default:
                icon.name = alias;
                icon.isCustom = false;
            }
            return icon;
        }
        var unsubscribe = $scope.$on('formSubmitting', function (ev, args) {
            var commands = _.where($scope.tinyMceConfig.commands, { selected: true });
            $scope.model.value.toolbar = _.pluck(commands, 'frontEndCommand');
        });
        // when the scope is destroyed we need to unsubscribe
        $scope.$on('$destroy', function () {
            unsubscribe();
        });
        // load TinyMCE skin which contains css for font-icons
        assetsService.loadCss('lib/tinymce/skins/umbraco/skin.min.css', $scope);
    });
    function sliderController($scope, $log, $element, assetsService, angularHelper) {
        //configure some defaults
        if (!$scope.model.config.orientation) {
            $scope.model.config.orientation = 'horizontal';
        }
        if (!$scope.model.config.enableRange) {
            $scope.model.config.enableRange = false;
        } else {
            $scope.model.config.enableRange = $scope.model.config.enableRange === '1' ? true : false;
        }
        if (!$scope.model.config.initVal1) {
            $scope.model.config.initVal1 = 0;
        } else {
            $scope.model.config.initVal1 = parseFloat($scope.model.config.initVal1);
        }
        if (!$scope.model.config.initVal2) {
            $scope.model.config.initVal2 = 0;
        } else {
            $scope.model.config.initVal2 = parseFloat($scope.model.config.initVal2);
        }
        if (!$scope.model.config.minVal) {
            $scope.model.config.minVal = 0;
        } else {
            $scope.model.config.minVal = parseFloat($scope.model.config.minVal);
        }
        if (!$scope.model.config.maxVal) {
            $scope.model.config.maxVal = 100;
        } else {
            $scope.model.config.maxVal = parseFloat($scope.model.config.maxVal);
        }
        if (!$scope.model.config.step) {
            $scope.model.config.step = 1;
        } else {
            $scope.model.config.step = parseFloat($scope.model.config.step);
        }
        if (!$scope.model.config.handle) {
            $scope.model.config.handle = 'round';
        }
        if (!$scope.model.config.reversed) {
            $scope.model.config.reversed = false;
        } else {
            $scope.model.config.reversed = $scope.model.config.reversed === '1' ? true : false;
        }
        if (!$scope.model.config.tooltip) {
            $scope.model.config.tooltip = 'show';
        }
        if (!$scope.model.config.tooltipSplit) {
            $scope.model.config.tooltipSplit = false;
        } else {
            $scope.model.config.tooltipSplit = $scope.model.config.tooltipSplit === '1' ? true : false;
        }
        if ($scope.model.config.tooltipFormat) {
            $scope.model.config.formatter = function (value) {
                if (angular.isArray(value) && $scope.model.config.enableRange) {
                    return $scope.model.config.tooltipFormat.replace('{0}', value[0]).replace('{1}', value[1]);
                } else {
                    return $scope.model.config.tooltipFormat.replace('{0}', value);
                }
            };
        }
        if (!$scope.model.config.ticks) {
            $scope.model.config.ticks = [];
        } else {
            // returns comma-separated string to an array, e.g. [0, 100, 200, 300, 400]
            $scope.model.config.ticks = _.map($scope.model.config.ticks.split(','), function (item) {
                return parseInt(item.trim());
            });
        }
        if (!$scope.model.config.ticksPositions) {
            $scope.model.config.ticksPositions = [];
        } else {
            // returns comma-separated string to an array, e.g. [0, 30, 60, 70, 90, 100]
            $scope.model.config.ticksPositions = _.map($scope.model.config.ticksPositions.split(','), function (item) {
                return parseInt(item.trim());
            });
        }
        if (!$scope.model.config.ticksLabels) {
            $scope.model.config.ticksLabels = [];
        } else {
            // returns comma-separated string to an array, e.g. ['$0', '$100', '$200', '$300', '$400']
            $scope.model.config.ticksLabels = _.map($scope.model.config.ticksLabels.split(','), function (item) {
                return item.trim();
            });
        }
        if (!$scope.model.config.ticksSnapBounds) {
            $scope.model.config.ticksSnapBounds = 0;
        } else {
            $scope.model.config.ticksSnapBounds = parseFloat($scope.model.config.ticksSnapBounds);
        }
        /** This creates the slider with the model values - it's called on startup and if the model value changes */
        function createSlider() {
            //the value that we'll give the slider - if it's a range, we store our value as a comma separated val but this slider expects an array
            var sliderVal = null;
            //configure the model value based on if range is enabled or not
            if ($scope.model.config.enableRange == true) {
                //If no value saved yet - then use default value
                //If it contains a single value - then also create a new array value
                if (!$scope.model.value || $scope.model.value.indexOf(',') == -1) {
                    var i1 = parseFloat($scope.model.config.initVal1);
                    var i2 = parseFloat($scope.model.config.initVal2);
                    sliderVal = [
                        isNaN(i1) ? $scope.model.config.minVal : i1 >= $scope.model.config.minVal ? i1 : $scope.model.config.minVal,
                        isNaN(i2) ? $scope.model.config.maxVal : i2 >= i1 ? i2 <= $scope.model.config.maxVal ? i2 : $scope.model.config.maxVal : $scope.model.config.maxVal
                    ];
                } else {
                    //this will mean it's a delimited value stored in the db, convert it to an array
                    sliderVal = _.map($scope.model.value.split(','), function (item) {
                        return parseFloat(item);
                    });
                }
            } else {
                //If no value saved yet - then use default value
                if ($scope.model.value) {
                    sliderVal = parseFloat($scope.model.value);
                } else {
                    sliderVal = $scope.model.config.initVal1;
                }
            }
            // Initialise model value if not set
            if (!$scope.model.value) {
                setModelValueFromSlider(sliderVal);
            }
            //initiate slider, add event handler and get the instance reference (stored in data)
            var slider = $element.find('.slider-item').bootstrapSlider({
                max: $scope.model.config.maxVal,
                min: $scope.model.config.minVal,
                orientation: $scope.model.config.orientation,
                selection: $scope.model.config.reversed ? 'after' : 'before',
                step: $scope.model.config.step,
                precision: $scope.model.config.precision,
                tooltip: $scope.model.config.tooltip,
                tooltip_split: $scope.model.config.tooltipSplit,
                tooltip_position: $scope.model.config.tooltipPosition,
                handle: $scope.model.config.handle,
                reversed: $scope.model.config.reversed,
                ticks: $scope.model.config.ticks,
                ticks_positions: $scope.model.config.ticksPositions,
                ticks_labels: $scope.model.config.ticksLabels,
                ticks_snap_bounds: $scope.model.config.ticksSnapBounds,
                formatter: $scope.model.config.formatter,
                range: $scope.model.config.enableRange,
                //set the slider val - we cannot do this with data- attributes when using ranges
                value: sliderVal
            }).on('slideStop', function (e) {
                var value = e.value;
                angularHelper.safeApply($scope, function () {
                    setModelValueFromSlider(value);
                });
            }).data('slider');
        }
        /** Called on start-up when no model value has been applied and on change of the slider via the UI - updates
        the model with the currently selected slider value(s) **/
        function setModelValueFromSlider(sliderVal) {
            //Get the value from the slider and format it correctly, if it is a range we want a comma delimited value
            if ($scope.model.config.enableRange == true) {
                $scope.model.value = sliderVal.join(',');
            } else {
                $scope.model.value = sliderVal.toString();
            }
        }
        //tell the assetsService to load the bootstrap slider
        //libs from the plugin folder
        assetsService.loadJs('lib/slider/js/bootstrap-slider.js').then(function () {
            createSlider();
            //here we declare a special method which will be called whenever the value has changed from the server
            //this is instead of doing a watch on the model.value = faster
            $scope.model.onValueChanged = function (newVal, oldVal) {
                if (newVal != oldVal) {
                    createSlider();
                }
            };
        });
        //load the separate css for the editor to avoid it blocking our js loading
        assetsService.loadCss('lib/slider/bootstrap-slider.css', $scope);
        assetsService.loadCss('lib/slider/bootstrap-slider-custom.css', $scope);
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.SliderController', sliderController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.TagsController', function ($rootScope, $scope, $log, assetsService, umbRequestHelper, angularHelper, $timeout, $element) {
        var $typeahead;
        $scope.isLoading = true;
        $scope.tagToAdd = '';
        function setModelValue(val) {
            $scope.model.value = val || $scope.model.value;
            if ($scope.model.value) {
                if (!$scope.model.config.storageType || $scope.model.config.storageType !== 'Json') {
                    //it is csv
                    if (!$scope.model.value) {
                        $scope.model.value = [];
                    } else {
                        if ($scope.model.value.length > 0) {
                            // split the csv string, and remove any duplicate values
                            var tempArray = $scope.model.value.split(',').map(function (v) {
                                return v.trim();
                            });
                            $scope.model.value = tempArray.filter(function (v, i, self) {
                                return self.indexOf(v) === i;
                            });
                        }
                    }
                }
            } else {
                $scope.model.value = [];
            }
        }
        assetsService.loadJs('lib/typeahead.js/typeahead.bundle.min.js', $scope).then(function () {
            $scope.isLoading = false;
            //load current value
            setModelValue();
            // Method required by the valPropertyValidator directive (returns true if the property editor has at least one tag selected)
            $scope.validateMandatory = function () {
                return {
                    isValid: !$scope.model.validation.mandatory || $scope.model.value != null && $scope.model.value.length > 0,
                    errorMsg: 'Value cannot be empty',
                    errorKey: 'required'
                };
            };
            //Helper method to add a tag on enter or on typeahead select
            function addTag(tagToAdd) {
                if (tagToAdd != null && tagToAdd.length > 0) {
                    if ($scope.model.value.indexOf(tagToAdd) < 0) {
                        $scope.model.value.push(tagToAdd);
                        //this is required to re-validate
                        $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
                    }
                }
            }
            $scope.addTagOnEnter = function (e) {
                var code = e.keyCode || e.which;
                if (code == 13) {
                    //Enter keycode
                    if ($element.find('.tags-' + $scope.model.alias).parent().find('.tt-dropdown-menu .tt-cursor').length === 0) {
                        //this is required, otherwise the html form will attempt to submit.
                        e.preventDefault();
                        $scope.addTag();
                    }
                }
            };
            $scope.addTag = function () {
                //ensure that we're not pressing the enter key whilst selecting a typeahead value from the drop down
                //we need to use jquery because typeahead duplicates the text box
                addTag($scope.tagToAdd);
                $scope.tagToAdd = '';
                //this clears the value stored in typeahead so it doesn't try to add the text again
                // https://issues.umbraco.org/issue/U4-4947
                $typeahead.typeahead('val', '');
            };
            // Set the visible prompt to -1 to ensure it will not be visible
            $scope.promptIsVisible = '-1';
            $scope.removeTag = function (tag) {
                var i = $scope.model.value.indexOf(tag);
                if (i >= 0) {
                    // Make sure to hide the prompt so it does not stay open because another item gets a new number in the array index
                    $scope.promptIsVisible = '-1';
                    // Remove the tag from the index
                    $scope.model.value.splice(i, 1);
                    //this is required to re-validate
                    $scope.propertyForm.tagCount.$setViewValue($scope.model.value.length);
                }
            };
            $scope.showPrompt = function (idx, tag) {
                var i = $scope.model.value.indexOf(tag);
                // Make the prompt visible for the clicked tag only
                if (i === idx) {
                    $scope.promptIsVisible = i;
                }
            };
            $scope.hidePrompt = function () {
                $scope.promptIsVisible = '-1';
            };
            //vice versa
            $scope.model.onValueChanged = function (newVal, oldVal) {
                //update the display val again if it has changed from the server
                setModelValue(newVal);
            };
            //configure the tags data source
            //helper method to format the data for bloodhound
            function dataTransform(list) {
                //transform the result to what bloodhound wants
                var tagList = _.map(list, function (i) {
                    return { value: i.text };
                });
                // remove current tags from the list
                return $.grep(tagList, function (tag) {
                    return $.inArray(tag.value, $scope.model.value) === -1;
                });
            }
            // helper method to remove current tags
            function removeCurrentTagsFromSuggestions(suggestions) {
                return $.grep(suggestions, function (suggestion) {
                    return $.inArray(suggestion.value, $scope.model.value) === -1;
                });
            }
            var tagsHound = new Bloodhound({
                datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
                queryTokenizer: Bloodhound.tokenizers.whitespace,
                dupDetector: function (remoteMatch, localMatch) {
                    return remoteMatch['value'] == localMatch['value'];
                },
                //pre-fetch the tags for this category
                prefetch: {
                    url: umbRequestHelper.getApiUrl('tagsDataBaseUrl', 'GetTags', [{ tagGroup: $scope.model.config.group }]),
                    //TTL = 5 minutes
                    ttl: 300000,
                    filter: dataTransform
                },
                //dynamically get the tags for this category (they may have changed on the server)
                remote: {
                    url: umbRequestHelper.getApiUrl('tagsDataBaseUrl', 'GetTags', [{ tagGroup: $scope.model.config.group }]),
                    filter: dataTransform
                }
            });
            tagsHound.initialize(true);
            //configure the type ahead
            $timeout(function () {
                $typeahead = $element.find('.tags-' + $scope.model.alias).typeahead({
                    //This causes some strangeness as it duplicates the textbox, best leave off for now.
                    hint: false,
                    highlight: true,
                    cacheKey: new Date(),
                    // Force a cache refresh each time the control is initialized
                    minLength: 1
                }, {
                    //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options
                    // name = the data set name, we'll make this the tag group name
                    name: $scope.model.config.group,
                    displayKey: 'value',
                    source: function (query, cb) {
                        tagsHound.get(query, function (suggestions) {
                            cb(removeCurrentTagsFromSuggestions(suggestions));
                        });
                    }
                }).bind('typeahead:selected', function (obj, datum, name) {
                    angularHelper.safeApply($scope, function () {
                        addTag(datum['value']);
                        $scope.tagToAdd = '';
                        // clear the typed text
                        $typeahead.typeahead('val', '');
                    });
                }).bind('typeahead:autocompleted', function (obj, datum, name) {
                    angularHelper.safeApply($scope, function () {
                        addTag(datum['value']);
                        $scope.tagToAdd = '';
                    });
                }).bind('typeahead:opened', function (obj) {
                });
            });
            $scope.$on('$destroy', function () {
                tagsHound.clearPrefetchCache();
                tagsHound.clearRemoteCache();
                $element.find('.tags-' + $scope.model.alias).typeahead('destroy');
                delete tagsHound;
            });
        });
    });
    //this controller simply tells the dialogs service to open a mediaPicker window
    //with a specified callback, this callback will receive an object with a selection on it
    angular.module('umbraco').controller('Umbraco.PropertyEditors.EmbeddedContentController', function ($rootScope, $scope, $log) {
        $scope.showForm = false;
        $scope.fakeData = [];
        $scope.create = function () {
            $scope.showForm = true;
            $scope.fakeData = angular.copy($scope.model.config.fields);
        };
        $scope.show = function () {
            $scope.showCode = true;
        };
        $scope.add = function () {
            $scope.showForm = false;
            if (!($scope.model.value instanceof Array)) {
                $scope.model.value = [];
            }
            $scope.model.value.push(angular.copy($scope.fakeData));
            $scope.fakeData = [];
        };
    });
    function textAreaController($scope) {
        // macro parameter editor doesn't contains a config object,
        // so we create a new one to hold any properties 
        if (!$scope.model.config) {
            $scope.model.config = {};
        }
        if (!$scope.model.config.maxChars) {
            $scope.model.config.maxChars = false;
        }
        $scope.model.maxlength = false;
        if ($scope.model.config && $scope.model.config.maxChars) {
            $scope.model.maxlength = true;
            if ($scope.model.value == undefined) {
                $scope.model.count = $scope.model.config.maxChars * 1;
            } else {
                $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
            }
        }
        $scope.model.change = function () {
            if ($scope.model.config && $scope.model.config.maxChars) {
                if ($scope.model.value == undefined) {
                    $scope.model.count = $scope.model.config.maxChars * 1;
                } else {
                    $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
                }
                if ($scope.model.count < 0) {
                    $scope.model.value = $scope.model.value.substring(0, $scope.model.config.maxChars * 1);
                    $scope.model.count = 0;
                }
            }
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.textAreaController', textAreaController);
    function textboxController($scope) {
        // macro parameter editor doesn't contains a config object,
        // so we create a new one to hold any properties
        if (!$scope.model.config) {
            $scope.model.config = {};
        }
        $scope.model.maxlength = false;
        if ($scope.model.config && $scope.model.config.maxChars) {
            $scope.model.maxlength = true;
        }
        if (!$scope.model.config.maxChars) {
            // 500 is the maximum number that can be stored
            // in the database, so set it to the max, even
            // if no max is specified in the config
            $scope.model.config.maxChars = 500;
        }
        if ($scope.model.maxlength) {
            if ($scope.model.value === undefined) {
                $scope.model.count = $scope.model.config.maxChars * 1;
            } else {
                $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
            }
        }
        $scope.model.change = function () {
            if ($scope.model.config && $scope.model.config.maxChars) {
                if ($scope.model.value === undefined) {
                    $scope.model.count = $scope.model.config.maxChars * 1;
                } else {
                    $scope.model.count = $scope.model.config.maxChars * 1 - $scope.model.value.length;
                }
                if ($scope.model.count < 0) {
                    $scope.model.value = $scope.model.value.substring(0, $scope.model.config.maxChars * 1);
                    $scope.model.count = 0;
                }
            }
        };
    }
    angular.module('umbraco').controller('Umbraco.PropertyEditors.textboxController', textboxController);
    angular.module('umbraco').controller('Umbraco.PropertyEditors.UrlListController', function ($rootScope, $scope, $filter) {
        function formatDisplayValue() {
            if (angular.isArray($scope.model.value)) {
                //it's the json value
                $scope.renderModel = _.map($scope.model.value, function (item) {
                    return {
                        url: item.url,
                        linkText: item.linkText,
                        urlTarget: item.target ? item.target : '_blank',
                        icon: item.icon ? item.icon : 'icon-out'
                    };
                });
            } else {
                //it's the default csv value
                $scope.renderModel = _.map($scope.model.value.split(','), function (item) {
                    return {
                        url: item,
                        linkText: '',
                        urlTarget: $scope.config && $scope.config.target ? $scope.config.target : '_blank',
                        icon: $scope.config && $scope.config.icon ? $scope.config.icon : 'icon-out'
                    };
                });
            }
        }
        $scope.getUrl = function (valueUrl) {
            if (valueUrl.indexOf('/') >= 0) {
                return valueUrl;
            }
            return '#';
        };
        formatDisplayValue();
        //here we declare a special method which will be called whenever the value has changed from the server
        //this is instead of doing a watch on the model.value = faster
        $scope.model.onValueChanged = function (newVal, oldVal) {
            //update the display val again
            formatDisplayValue();
        };
    });
    (function () {
        'use strict';
        function ScriptsCreateController($scope, $location, navigationService, formHelper, codefileResource, localizationService, appState) {
            var vm = this;
            var node = $scope.dialogOptions.currentNode;
            var localizeCreateFolder = localizationService.localize('defaultdialog_createFolder');
            vm.creatingFolder = false;
            vm.folderName = '';
            vm.createFolderError = '';
            vm.fileExtension = '';
            vm.createFile = createFile;
            vm.showCreateFolder = showCreateFolder;
            vm.createFolder = createFolder;
            function createFile() {
                $location.path('/settings/scripts/edit/' + node.id).search('create', 'true');
                navigationService.hideMenu();
            }
            function showCreateFolder() {
                vm.creatingFolder = true;
            }
            function createFolder(form) {
                if (formHelper.submitForm({
                        scope: $scope,
                        formCtrl: form,
                        statusMessage: localizeCreateFolder
                    })) {
                    codefileResource.createContainer('scripts', node.id, vm.folderName).then(function (saved) {
                        navigationService.hideMenu();
                        navigationService.syncTree({
                            tree: 'scripts',
                            path: saved.path,
                            forceReload: true,
                            activate: true
                        });
                        formHelper.resetForm({ scope: $scope });
                        var section = appState.getSectionState('currentSection');
                    }, function (err) {
                        vm.createFolderError = err;
                        formHelper.showNotifications(err.data);
                    });
                }
            }
        }
        angular.module('umbraco').controller('Umbraco.Editors.Scripts.CreateController', ScriptsCreateController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Scripts.DeleteController
 * @function
 *
 * @description
 * The controller for deleting scripts
 */
    function ScriptsDeleteController($scope, codefileResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            codefileResource.deleteByPath('scripts', $scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Scripts.DeleteController', ScriptsDeleteController);
    (function () {
        'use strict';
        function ScriptsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService, templateHelper, angularHelper) {
            var vm = this;
            var currentPosition = null;
            var localizeSaving = localizationService.localize('general_saving');
            vm.page = {};
            vm.page.loading = true;
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            vm.page.saveButtonState = 'init';
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts());
            vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts());
            vm.script = {};
            // bind functions to view model
            vm.save = save;
            /* Function bound to view model */
            function save() {
                vm.page.saveButtonState = 'busy';
                vm.script.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    statusMessage: localizeSaving,
                    saveMethod: codefileResource.save,
                    scope: $scope,
                    content: vm.script,
                    // We do not redirect on failure for scripts - this is because it is not possible to actually save the script
                    // when server side validation fails - as opposed to content where we are capable of saving the content
                    // item if server side validation fails
                    redirectOnFailure: false,
                    rebindCallback: function (orignal, saved) {
                    }
                }).then(function (saved) {
                    localizationService.localizeMany([
                        'speechBubbles_fileSavedHeader',
                        'speechBubbles_fileSavedText'
                    ]).then(function (data) {
                        var header = data[0];
                        var message = data[1];
                        notificationsService.success(header, message);
                    });
                    //check if the name changed, if so we need to redirect
                    if (vm.script.id !== saved.id) {
                        contentEditingHelper.redirectToRenamedContent(saved.id);
                    } else {
                        vm.page.saveButtonState = 'success';
                        vm.script = saved;
                        //sync state
                        editorState.set(vm.script);
                        // sync tree
                        navigationService.syncTree({
                            tree: 'scripts',
                            path: vm.script.path,
                            forceReload: true
                        }).then(function (syncArgs) {
                            vm.page.menu.currentNode = syncArgs.node;
                        });
                    }
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                    localizationService.localizeMany([
                        'speechBubbles_validationFailedHeader',
                        'speechBubbles_validationFailedMessage'
                    ]).then(function (data) {
                        var header = data[0];
                        var message = data[1];
                        notificationsService.error(header, message);
                    });
                });
            }
            /* Local functions */
            function init() {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                if ($routeParams.create) {
                    codefileResource.getScaffold('scripts', $routeParams.id).then(function (script) {
                        ready(script, false);
                    });
                } else {
                    codefileResource.getByPath('scripts', $routeParams.id).then(function (script) {
                        ready(script, true);
                    });
                }
            }
            function ready(script, syncTree) {
                vm.page.loading = false;
                vm.script = script;
                //sync state
                editorState.set(vm.script);
                if (syncTree) {
                    navigationService.syncTree({
                        tree: 'scripts',
                        path: vm.script.path,
                        forceReload: true
                    }).then(function (syncArgs) {
                        vm.page.menu.currentNode = syncArgs.node;
                    });
                }
                vm.aceOption = {
                    mode: 'javascript',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: {
                        fontSize: '14px',
                        enableSnippets: true,
                        enableBasicAutocompletion: true,
                        enableLiveAutocompletion: false
                    },
                    onLoad: function (_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        //Unassigns the keybinding (That was previously auto-complete)
                        //As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        //TODO: Move all these keybinding config out into some helper/service
                        _editor.commands.addCommands([//Disable (alt+shift+K)
                            //Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function () {
                                    //Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            }]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new script
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                            });
                        }
                        vm.editor.on('change', changeAceEditor);
                    }
                };
                function changeAceEditor() {
                    setFormState('dirty');
                }
                function setFormState(state) {
                    // get the current form
                    var currentForm = angularHelper.getCurrentForm($scope);
                    // set state
                    if (state === 'dirty') {
                        currentForm.$setDirty();
                    } else if (state === 'pristine') {
                        currentForm.$setPristine();
                    }
                }
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Scripts.EditController', ScriptsEditController);
    }());
    /**
 * @ngdoc controller
 * @name Umbraco.Editors.Templates.DeleteController
 * @function
 *
 * @description
 * The controller for the template delete dialog
 */
    function TemplatesDeleteController($scope, templateResource, treeService, navigationService) {
        $scope.performDelete = function () {
            //mark it for deletion (used in the UI)
            $scope.currentNode.loading = true;
            // Reset the error message
            $scope.error = null;
            templateResource.deleteById($scope.currentNode.id).then(function () {
                $scope.currentNode.loading = false;
                //get the root node before we remove it
                var rootNode = treeService.getTreeRoot($scope.currentNode);
                //TODO: Need to sync tree, etc...
                treeService.removeNode($scope.currentNode);
                navigationService.hideMenu();
            }, function (err) {
                $scope.currentNode.loading = false;
                $scope.error = err;
            });
        };
        $scope.cancel = function () {
            navigationService.hideDialog();
        };
    }
    angular.module('umbraco').controller('Umbraco.Editors.Templates.DeleteController', TemplatesDeleteController);
    (function () {
        'use strict';
        function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper) {
            var vm = this;
            var oldMasterTemplateAlias = null;
            var localizeSaving = localizationService.localize('general_saving');
            vm.page = {};
            vm.page.loading = true;
            vm.templates = [];
            //menu
            vm.page.menu = {};
            vm.page.menu.currentSection = appState.getSectionState('currentSection');
            vm.page.menu.currentNode = null;
            //Used to toggle the keyboard shortcut modal
            //From a custom keybinding in ace editor - that conflicts with our own to show the dialog
            vm.showKeyboardShortcut = false;
            //Keyboard shortcuts for help dialog
            vm.page.keyboardShortcutsOverview = [];
            vm.page.keyboardShortcutsOverview.push(templateHelper.getGeneralShortcuts());
            vm.page.keyboardShortcutsOverview.push(templateHelper.getEditorShortcuts());
            vm.page.keyboardShortcutsOverview.push(templateHelper.getTemplateEditorShortcuts());
            vm.save = function (suppressNotification) {
                vm.page.saveButtonState = 'busy';
                vm.template.content = vm.editor.getValue();
                contentEditingHelper.contentEditorPerformSave({
                    statusMessage: localizeSaving,
                    saveMethod: templateResource.save,
                    scope: $scope,
                    content: vm.template,
                    //We do not redirect on failure for templates - this is because it is not possible to actually save the template
                    // type when server side validation fails - as opposed to content where we are capable of saving the content
                    // item if server side validation fails
                    redirectOnFailure: false,
                    rebindCallback: function (orignal, saved) {
                    }
                }).then(function (saved) {
                    if (!suppressNotification) {
                        localizationService.localizeMany([
                            'speechBubbles_templateSavedHeader',
                            'speechBubbles_templateSavedText'
                        ]).then(function (data) {
                            var header = data[0];
                            var message = data[1];
                            notificationsService.success(header, message);
                        });
                    }
                    vm.page.saveButtonState = 'success';
                    vm.template = saved;
                    //sync state
                    editorState.set(vm.template);
                    // sync tree
                    // if master template alias has changed move the node to it's new location
                    if (oldMasterTemplateAlias !== vm.template.masterTemplateAlias) {
                        // When creating a new template the id is -1. Make sure We don't remove the root node.
                        if (vm.page.menu.currentNode.id !== '-1') {
                            // move node to new location in tree
                            //first we need to remove the node that we're working on
                            treeService.removeNode(vm.page.menu.currentNode);
                        }
                        // update stored alias to the new one so the node won't move again unless the alias is changed again
                        oldMasterTemplateAlias = vm.template.masterTemplateAlias;
                        navigationService.syncTree({
                            tree: 'templates',
                            path: vm.template.path,
                            forceReload: true,
                            activate: true
                        }).then(function (args) {
                            vm.page.menu.currentNode = args.node;
                        });
                    } else {
                        // normal tree sync
                        navigationService.syncTree({
                            tree: 'templates',
                            path: vm.template.path,
                            forceReload: true
                        }).then(function (syncArgs) {
                            vm.page.menu.currentNode = syncArgs.node;
                        });
                    }
                    // clear $dirty state on form
                    setFormState('pristine');
                }, function (err) {
                    if (suppressNotification) {
                        vm.page.saveButtonState = 'error';
                        localizationService.localizeMany([
                            'speechBubbles_validationFailedHeader',
                            'speechBubbles_validationFailedMessage'
                        ]).then(function (data) {
                            var header = data[0];
                            var message = data[1];
                            notificationsService.error(header, message);
                        });
                    }
                });
            };
            vm.init = function () {
                //we need to load this somewhere, for now its here.
                assetsService.loadCss('lib/ace-razor-mode/theme/razor_chrome.css', $scope);
                //load templates - used in the master template picker
                templateResource.getAll().then(function (templates) {
                    vm.templates = templates;
                });
                if ($routeParams.create) {
                    templateResource.getScaffold($routeParams.id).then(function (template) {
                        vm.ready(template);
                    });
                } else {
                    templateResource.getById($routeParams.id).then(function (template) {
                        vm.ready(template);
                    });
                }
            };
            vm.ready = function (template) {
                vm.page.loading = false;
                vm.template = template;
                // if this is a new template, bind to the blur event on the name
                if ($routeParams.create) {
                    $timeout(function () {
                        var nameField = angular.element(document.querySelector('[data-element="editor-name-field"]'));
                        if (nameField) {
                            nameField.bind('blur', function (event) {
                                if (event.target.value) {
                                    vm.save(true);
                                }
                            });
                        }
                    });
                }
                //sync state
                editorState.set(vm.template);
                navigationService.syncTree({
                    tree: 'templates',
                    path: vm.template.path,
                    forceReload: true
                }).then(function (syncArgs) {
                    vm.page.menu.currentNode = syncArgs.node;
                });
                // save state of master template to use for comparison when syncing the tree on save
                oldMasterTemplateAlias = angular.copy(template.masterTemplateAlias);
                // ace configuration
                vm.aceOption = {
                    mode: 'razor',
                    theme: 'chrome',
                    showPrintMargin: false,
                    advanced: {
                        fontSize: '14px',
                        enableSnippets: false,
                        //The Razor mode snippets are awful (Need a way to override these)
                        enableBasicAutocompletion: true,
                        enableLiveAutocompletion: false
                    },
                    onLoad: function (_editor) {
                        vm.editor = _editor;
                        //Update the auto-complete method to use ctrl+alt+space
                        _editor.commands.bindKey('ctrl-alt-space', 'startAutocomplete');
                        //Unassigns the keybinding (That was previously auto-complete)
                        //As conflicts with our own tree search shortcut
                        _editor.commands.bindKey('ctrl-space', null);
                        // Assign new keybinding
                        _editor.commands.addCommands([
                            //Disable (alt+shift+K)
                            //Conflicts with our own show shortcuts dialog - this overrides it
                            {
                                name: 'unSelectOrFindPrevious',
                                bindKey: 'Alt-Shift-K',
                                exec: function () {
                                    //Toggle the show keyboard shortcuts overlay
                                    $scope.$apply(function () {
                                        vm.showKeyboardShortcut = !vm.showKeyboardShortcut;
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoValue',
                                bindKey: 'Alt-Shift-V',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openPageFieldOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertPartialView',
                                bindKey: 'Alt-Shift-P',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openPartialOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertDictionary',
                                bindKey: 'Alt-Shift-D',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openDictionaryItemOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertUmbracoMacro',
                                bindKey: 'Alt-Shift-M',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openMacroOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertQuery',
                                bindKey: 'Alt-Shift-Q',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openQueryBuilderOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'insertSection',
                                bindKey: 'Alt-Shift-S',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openSectionsOverlay();
                                    });
                                },
                                readOnly: true
                            },
                            {
                                name: 'chooseMasterTemplate',
                                bindKey: 'Alt-Shift-T',
                                exec: function () {
                                    $scope.$apply(function () {
                                        openMasterTemplateOverlay();
                                    });
                                },
                                readOnly: true
                            }
                        ]);
                        // initial cursor placement
                        // Keep cursor in name field if we are create a new template
                        // else set the cursor at the bottom of the code editor
                        if (!$routeParams.create) {
                            $timeout(function () {
                                vm.editor.navigateFileEnd();
                                vm.editor.focus();
                                persistCurrentLocation();
                            });
                        }
                        //change on blur, focus
                        vm.editor.on('blur', persistCurrentLocation);
                        vm.editor.on('focus', persistCurrentLocation);
                        vm.editor.on('change', changeAceEditor);
                    }
                };
            };
            vm.openPageFieldOverlay = openPageFieldOverlay;
            vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
            vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
            vm.openMacroOverlay = openMacroOverlay;
            vm.openInsertOverlay = openInsertOverlay;
            vm.openSectionsOverlay = openSectionsOverlay;
            vm.openPartialOverlay = openPartialOverlay;
            vm.openMasterTemplateOverlay = openMasterTemplateOverlay;
            vm.selectMasterTemplate = selectMasterTemplate;
            vm.getMasterTemplateName = getMasterTemplateName;
            vm.removeMasterTemplate = removeMasterTemplate;
            function openInsertOverlay() {
                vm.insertOverlay = {
                    view: 'insert',
                    allowedTypes: {
                        macro: true,
                        dictionary: true,
                        partial: true,
                        umbracoField: true
                    },
                    hideSubmitButton: true,
                    show: true,
                    submit: function (model) {
                        switch (model.insert.type) {
                        case 'macro':
                            var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, 'Mvc');
                            insert(macroObject.syntax);
                            break;
                        case 'dictionary':
                            var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
                            insert(code);
                            break;
                        case 'partial':
                            var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name);
                            insert(code);
                            break;
                        case 'umbracoField':
                            insert(model.insert.umbracoField);
                            break;
                        }
                        vm.insertOverlay.show = false;
                        vm.insertOverlay = null;
                    },
                    close: function (oldModel) {
                        // close the dialog
                        vm.insertOverlay.show = false;
                        vm.insertOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openMacroOverlay() {
                vm.macroPickerOverlay = {
                    view: 'macropicker',
                    dialogData: {},
                    show: true,
                    title: localizationService.localize('template_insertMacro'),
                    submit: function (model) {
                        var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, 'Mvc');
                        insert(macroObject.syntax);
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                    },
                    close: function (oldModel) {
                        // close the dialog
                        vm.macroPickerOverlay.show = false;
                        vm.macroPickerOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openPageFieldOverlay() {
                vm.pageFieldOverlay = {
                    submitButtonLabel: 'Insert',
                    closeButtonlabel: 'Cancel',
                    view: 'insertfield',
                    show: true,
                    title: localizationService.localize('template_insertPageField'),
                    submit: function (model) {
                        insert(model.umbracoField);
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                    },
                    close: function (model) {
                        // close the dialog
                        vm.pageFieldOverlay.show = false;
                        vm.pageFieldOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openDictionaryItemOverlay() {
                vm.dictionaryItemOverlay = {
                    view: 'treepicker',
                    section: 'settings',
                    treeAlias: 'dictionary',
                    entityType: 'dictionary',
                    multiPicker: false,
                    show: true,
                    title: localizationService.localize('template_insertDictionaryItem'),
                    emptyStateMessage: localizationService.localize('emptyStates_emptyDictionaryTree'),
                    select: function (node) {
                        var code = templateHelper.getInsertDictionarySnippet(node.name);
                        insert(code);
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.dictionaryItemOverlay.show = false;
                        vm.dictionaryItemOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openPartialOverlay() {
                vm.partialItemOverlay = {
                    view: 'treepicker',
                    section: 'settings',
                    treeAlias: 'partialViews',
                    entityType: 'partialView',
                    multiPicker: false,
                    show: true,
                    title: localizationService.localize('template_insertPartialView'),
                    filter: function (i) {
                        if (i.name.indexOf('.cshtml') === -1 && i.name.indexOf('.vbhtml') === -1) {
                            return true;
                        }
                    },
                    filterCssClass: 'not-allowed',
                    select: function (node) {
                        var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name);
                        insert(code);
                        vm.partialItemOverlay.show = false;
                        vm.partialItemOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.partialItemOverlay.show = false;
                        vm.partialItemOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openQueryBuilderOverlay() {
                vm.queryBuilderOverlay = {
                    view: 'querybuilder',
                    show: true,
                    title: localizationService.localize('template_queryBuilder'),
                    submit: function (model) {
                        var code = templateHelper.getQuerySnippet(model.result.queryExpression);
                        insert(code);
                        vm.queryBuilderOverlay.show = false;
                        vm.queryBuilderOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.queryBuilderOverlay.show = false;
                        vm.queryBuilderOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openSectionsOverlay() {
                vm.sectionsOverlay = {
                    view: 'templatesections',
                    isMaster: vm.template.isMasterTemplate,
                    submitButtonLabel: 'Insert',
                    show: true,
                    submit: function (model) {
                        if (model.insertType === 'renderBody') {
                            var code = templateHelper.getRenderBodySnippet();
                            insert(code);
                        }
                        if (model.insertType === 'renderSection') {
                            var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection);
                            insert(code);
                        }
                        if (model.insertType === 'addSection') {
                            var code = templateHelper.getAddSectionSnippet(model.sectionName);
                            wrap(code);
                        }
                        vm.sectionsOverlay.show = false;
                        vm.sectionsOverlay = null;
                    },
                    close: function (model) {
                        // close dialog
                        vm.sectionsOverlay.show = false;
                        vm.sectionsOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function openMasterTemplateOverlay() {
                // make collection of available master templates
                var availableMasterTemplates = [];
                // filter out the current template and the selected master template
                angular.forEach(vm.templates, function (template) {
                    if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) {
                        var templatePathArray = template.path.split(',');
                        // filter descendant templates of current template
                        if (templatePathArray.indexOf(String(vm.template.id)) === -1) {
                            availableMasterTemplates.push(template);
                        }
                    }
                });
                vm.masterTemplateOverlay = {
                    view: 'itempicker',
                    title: localizationService.localize('template_mastertemplate'),
                    availableItems: availableMasterTemplates,
                    show: true,
                    submit: function (model) {
                        var template = model.selectedItem;
                        if (template && template.alias) {
                            vm.template.masterTemplateAlias = template.alias;
                            setLayout(template.alias + '.cshtml');
                        } else {
                            vm.template.masterTemplateAlias = null;
                            setLayout(null);
                        }
                        vm.masterTemplateOverlay.show = false;
                        vm.masterTemplateOverlay = null;
                    },
                    close: function (oldModel) {
                        // close dialog
                        vm.masterTemplateOverlay.show = false;
                        vm.masterTemplateOverlay = null;
                        // focus editor
                        vm.editor.focus();
                    }
                };
            }
            function selectMasterTemplate(template) {
                if (template && template.alias) {
                    vm.template.masterTemplateAlias = template.alias;
                    setLayout(template.alias + '.cshtml');
                } else {
                    vm.template.masterTemplateAlias = null;
                    setLayout(null);
                }
            }
            function getMasterTemplateName(masterTemplateAlias, templates) {
                if (masterTemplateAlias) {
                    var templateName = '';
                    angular.forEach(templates, function (template) {
                        if (template.alias === masterTemplateAlias) {
                            templateName = template.name;
                        }
                    });
                    return templateName;
                }
            }
            function removeMasterTemplate() {
                vm.template.masterTemplateAlias = null;
                // call set layout with no paramters to set layout to null
                setLayout();
            }
            function setLayout(templatePath) {
                var templateCode = vm.editor.getValue();
                var newValue = templatePath;
                var layoutDefRegex = new RegExp('(@{[\\s\\S]*?Layout\\s*?=\\s*?)("[^"]*?"|null)(;[\\s\\S]*?})', 'gi');
                if (newValue !== undefined && newValue !== '') {
                    if (layoutDefRegex.test(templateCode)) {
                        // Declaration exists, so just update it
                        templateCode = templateCode.replace(layoutDefRegex, '$1"' + newValue + '"$3');
                    } else {
                        // Declaration doesn't exist, so prepend to start of doc
                        //TODO: Maybe insert at the cursor position, rather than just at the top of the doc?
                        templateCode = '@{\n\tLayout = "' + newValue + '";\n}\n' + templateCode;
                    }
                } else {
                    if (layoutDefRegex.test(templateCode)) {
                        // Declaration exists, so just update it
                        templateCode = templateCode.replace(layoutDefRegex, '$1null$3');
                    }
                }
                vm.editor.setValue(templateCode);
                vm.editor.clearSelection();
                vm.editor.navigateFileStart();
                vm.editor.focus();
                // set form state to $dirty
                setFormState('dirty');
            }
            function insert(str) {
                vm.editor.focus();
                vm.editor.moveCursorToPosition(vm.currentPosition);
                vm.editor.insert(str);
                // set form state to $dirty
                setFormState('dirty');
            }
            function wrap(str) {
                var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange());
                str = str.replace('{0}', selectedContent);
                vm.editor.insert(str);
                vm.editor.focus();
                // set form state to $dirty
                setFormState('dirty');
            }
            function persistCurrentLocation() {
                vm.currentPosition = vm.editor.getCursorPosition();
            }
            function changeAceEditor() {
                setFormState('dirty');
            }
            function setFormState(state) {
                // get the current form
                var currentForm = angularHelper.getCurrentForm($scope);
                // set state
                if (state === 'dirty') {
                    currentForm.$setDirty();
                } else if (state === 'pristine') {
                    currentForm.$setPristine();
                }
            }
            vm.init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Templates.EditController', TemplatesEditController);
    }());
    (function () {
        'use strict';
        function UserGroupEditController($scope, $location, $routeParams, userGroupsResource, localizationService, contentEditingHelper) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            vm.page = {};
            vm.page.rootIcon = 'icon-folder';
            vm.userGroup = {};
            vm.labels = {};
            vm.goToPage = goToPage;
            vm.openSectionPicker = openSectionPicker;
            vm.openContentPicker = openContentPicker;
            vm.openMediaPicker = openMediaPicker;
            vm.openUserPicker = openUserPicker;
            vm.removeSelectedItem = removeSelectedItem;
            vm.clearStartNode = clearStartNode;
            vm.save = save;
            vm.openGranularPermissionsPicker = openGranularPermissionsPicker;
            vm.setPermissionsForNode = setPermissionsForNode;
            function init() {
                vm.loading = true;
                var labelKeys = [
                    'general_cancel',
                    'defaultdialogs_selectContentStartNode',
                    'defaultdialogs_selectMediaStartNode',
                    'defaultdialogs_selectNode',
                    'general_groups',
                    'content_contentRoot',
                    'media_mediaRoot'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    vm.labels.cancel = values[0];
                    vm.labels.selectContentStartNode = values[1];
                    vm.labels.selectMediaStartNode = values[2];
                    vm.labels.selectNode = values[3];
                    vm.labels.groups = values[4];
                    vm.labels.contentRoot = values[5];
                    vm.labels.mediaRoot = values[6];
                });
                localizationService.localize('general_add').then(function (name) {
                    vm.labels.add = name;
                });
                localizationService.localize('user_noStartNode').then(function (name) {
                    vm.labels.noStartNode = name;
                });
                if ($routeParams.create) {
                    // get user group scaffold
                    userGroupsResource.getUserGroupScaffold().then(function (userGroup) {
                        vm.userGroup = userGroup;
                        setSectionIcon(vm.userGroup.sections);
                        makeBreadcrumbs();
                        vm.loading = false;
                    });
                } else {
                    // get user group
                    userGroupsResource.getUserGroup($routeParams.id).then(function (userGroup) {
                        vm.userGroup = userGroup;
                        formatGranularPermissionSelection();
                        setSectionIcon(vm.userGroup.sections);
                        makeBreadcrumbs();
                        vm.loading = false;
                    });
                }
            }
            function save() {
                vm.page.saveButtonState = 'busy';
                contentEditingHelper.contentEditorPerformSave({
                    statusMessage: localizeSaving,
                    saveMethod: userGroupsResource.saveUserGroup,
                    scope: $scope,
                    content: vm.userGroup,
                    // We do not redirect on failure for users - this is because it is not possible to actually save a user
                    // when server side validation fails - as opposed to content where we are capable of saving the content
                    // item if server side validation fails
                    redirectOnFailure: false,
                    rebindCallback: function (orignal, saved) {
                    }
                }).then(function (saved) {
                    vm.userGroup = saved;
                    formatGranularPermissionSelection();
                    setSectionIcon(vm.userGroup.sections);
                    makeBreadcrumbs();
                    vm.page.saveButtonState = 'success';
                }, function (err) {
                    vm.page.saveButtonState = 'error';
                });
            }
            function goToPage(ancestor) {
                $location.path(ancestor.path).search('subview', ancestor.subView);
            }
            function openSectionPicker() {
                vm.sectionPicker = {
                    view: 'sectionpicker',
                    selection: vm.userGroup.sections,
                    closeButtonLabel: vm.labels.cancel,
                    show: true,
                    submit: function (model) {
                        vm.sectionPicker.show = false;
                        vm.sectionPicker = null;
                    },
                    close: function (oldModel) {
                        if (oldModel.selection) {
                            vm.userGroup.sections = oldModel.selection;
                        }
                        vm.sectionPicker.show = false;
                        vm.sectionPicker = null;
                    }
                };
            }
            function openContentPicker() {
                vm.contentPicker = {
                    title: vm.labels.selectContentStartNode,
                    view: 'contentpicker',
                    hideSubmitButton: true,
                    hideHeader: false,
                    show: true,
                    submit: function (model) {
                        if (model.selection) {
                            vm.userGroup.contentStartNode = model.selection[0];
                            if (vm.userGroup.contentStartNode.id === '-1') {
                                vm.userGroup.contentStartNode.name = vm.labels.contentRoot;
                                vm.userGroup.contentStartNode.icon = 'icon-folder';
                            }
                        }
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    },
                    close: function (oldModel) {
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    }
                };
            }
            function openMediaPicker() {
                vm.contentPicker = {
                    title: vm.labels.selectMediaStartNode,
                    view: 'treepicker',
                    section: 'media',
                    treeAlias: 'media',
                    entityType: 'media',
                    hideSubmitButton: true,
                    hideHeader: false,
                    show: true,
                    submit: function (model) {
                        if (model.selection) {
                            vm.userGroup.mediaStartNode = model.selection[0];
                            if (vm.userGroup.mediaStartNode.id === '-1') {
                                vm.userGroup.mediaStartNode.name = vm.labels.mediaRoot;
                                vm.userGroup.mediaStartNode.icon = 'icon-folder';
                            }
                        }
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    },
                    close: function (oldModel) {
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    }
                };
            }
            function openUserPicker() {
                vm.userPicker = {
                    view: 'userpicker',
                    selection: vm.userGroup.users,
                    show: true,
                    submit: function (model) {
                        vm.userPicker.show = false;
                        vm.userPicker = null;
                    },
                    close: function (oldModel) {
                        vm.userPicker.show = false;
                        vm.userPicker = null;
                    }
                };
            }
            /**
         * The granular permissions structure gets returned from the server in the dictionary format with each key being the permission category
         * however the list to display the permissions isn't via the dictionary way so we need to format it
         */
            function formatGranularPermissionSelection() {
                angular.forEach(vm.userGroup.assignedPermissions, function (node) {
                    formatGranularPermissionSelectionForNode(node);
                });
            }
            function formatGranularPermissionSelectionForNode(node) {
                //the dictionary is assigned via node.permissions we will reformat to node.allowedPermissions
                node.allowedPermissions = [];
                angular.forEach(node.permissions, function (permissions, key) {
                    angular.forEach(permissions, function (p) {
                        if (p.checked) {
                            node.allowedPermissions.push(p);
                        }
                    });
                });
            }
            function openGranularPermissionsPicker() {
                vm.contentPicker = {
                    title: vm.labels.selectNode,
                    view: 'contentpicker',
                    hideSubmitButton: true,
                    show: true,
                    submit: function (model) {
                        if (model.selection) {
                            var node = model.selection[0];
                            //check if this is already in our selection
                            var found = _.find(vm.userGroup.assignedPermissions, function (i) {
                                return i.id === node.id;
                            });
                            node = found ? found : node;
                            setPermissionsForNode(node);
                        }
                    },
                    close: function (oldModel) {
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    }
                };
            }
            function setPermissionsForNode(node) {
                //clone the current defaults to pass to the model
                if (!node.permissions) {
                    node.permissions = angular.copy(vm.userGroup.defaultPermissions);
                }
                vm.nodePermissions = {
                    view: 'nodepermissions',
                    node: node,
                    show: true,
                    submit: function (model) {
                        if (model && model.node && model.node.permissions) {
                            formatGranularPermissionSelectionForNode(node);
                            if (!vm.userGroup.assignedPermissions) {
                                vm.userGroup.assignedPermissions = [];
                            }
                            //check if this is already in our selection
                            var found = _.find(vm.userGroup.assignedPermissions, function (i) {
                                return i.id === node.id;
                            });
                            if (!found) {
                                vm.userGroup.assignedPermissions.push(node);
                            }
                        }
                        // close node permisssions overlay
                        vm.nodePermissions.show = false;
                        vm.nodePermissions = null;
                        // close content picker overlay
                        if (vm.contentPicker) {
                            vm.contentPicker.show = false;
                            vm.contentPicker = null;
                        }
                    },
                    close: function (oldModel) {
                        vm.nodePermissions.show = false;
                        vm.nodePermissions = null;
                    }
                };
            }
            function removeSelectedItem(index, selection) {
                if (selection && selection.length > 0) {
                    selection.splice(index, 1);
                }
            }
            function clearStartNode(type) {
                if (type === 'content') {
                    vm.userGroup.contentStartNode = null;
                } else if (type === 'media') {
                    vm.userGroup.mediaStartNode = null;
                }
            }
            function makeBreadcrumbs() {
                vm.breadcrumbs = [
                    {
                        'name': vm.labels.groups,
                        'path': '/users/users/overview',
                        'subView': 'groups'
                    },
                    { 'name': vm.userGroup.name }
                ];
            }
            function setSectionIcon(sections) {
                angular.forEach(sections, function (section) {
                    section.icon = 'icon-section ' + section.cssclass;
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.GroupController', UserGroupEditController);
    }());
    (function () {
        'use strict';
        function UsersOverviewController($scope, $location, $timeout, navigationService, localizationService) {
            var vm = this;
            var usersUri = $location.search().subview;
            if (!usersUri) {
                $location.search('subview', 'users');
                //exit after this, we don't want to initialize anything further since this
                //is going to change the route
                return;
            }
            //note on the below, we dont assign a view unless it's the right route since if we did that it will load in that controller
            //for the view which is unecessary and will cause extra overhead/requests to occur
            vm.page = {};
            vm.page.name = localizationService.localize('user_userManagement');
            vm.page.navigation = [
                {
                    'name': localizationService.localize('sections_users'),
                    'icon': 'icon-user',
                    'action': function () {
                        $location.search('subview', 'users');
                    },
                    'view': !usersUri || usersUri === 'users' ? 'views/users/views/users/users.html' : null,
                    'active': !usersUri || usersUri === 'users'
                },
                {
                    'name': localizationService.localize('general_groups'),
                    'icon': 'icon-users',
                    'action': function () {
                        $location.search('subview', 'groups');
                    },
                    'view': usersUri === 'groups' ? 'views/users/views/groups/groups.html' : null,
                    'active': usersUri === 'groups'
                }
            ];
            function init() {
                $timeout(function () {
                    navigationService.syncTree({
                        tree: 'users',
                        path: '-1'
                    });
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.OverviewController', UsersOverviewController);
    }());
    (function () {
        'use strict';
        function UserEditController($scope, eventsService, $q, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) {
            var vm = this;
            vm.page = {};
            vm.page.rootIcon = 'icon-folder';
            vm.user = { changePassword: null };
            vm.breadcrumbs = [];
            vm.avatarFile = {};
            vm.labels = {};
            vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + 'KB';
            vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
            vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
            //create the initial model for change password
            vm.changePasswordModel = {
                config: {},
                isChanging: false
            };
            vm.goToPage = goToPage;
            vm.openUserGroupPicker = openUserGroupPicker;
            vm.openContentPicker = openContentPicker;
            vm.openMediaPicker = openMediaPicker;
            vm.removeSelectedItem = removeSelectedItem;
            vm.disableUser = disableUser;
            vm.enableUser = enableUser;
            vm.unlockUser = unlockUser;
            vm.resendInvite = resendInvite;
            vm.deleteNonLoggedInUser = deleteNonLoggedInUser;
            vm.changeAvatar = changeAvatar;
            vm.clearAvatar = clearAvatar;
            vm.save = save;
            vm.toggleChangePassword = toggleChangePassword;
            function init() {
                vm.loading = true;
                var labelKeys = [
                    'general_saving',
                    'general_cancel',
                    'defaultdialogs_selectContentStartNode',
                    'defaultdialogs_selectMediaStartNode',
                    'sections_users',
                    'content_contentRoot',
                    'media_mediaRoot',
                    'user_noStartNodes',
                    'user_defaultInvitationMessage',
                    'user_deleteUserConfirmation'
                ];
                localizationService.localizeMany(labelKeys).then(function (values) {
                    vm.labels.saving = values[0];
                    vm.labels.cancel = values[1];
                    vm.labels.selectContentStartNode = values[2];
                    vm.labels.selectMediaStartNode = values[3];
                    vm.labels.users = values[4];
                    vm.labels.contentRoot = values[5];
                    vm.labels.mediaRoot = values[6];
                    vm.labels.noStartNodes = values[7];
                    vm.labels.defaultInvitationMessage = values[8];
                    vm.labels.deleteUserConfirmation = values[9];
                });
                // get user
                usersResource.getUser($routeParams.id).then(function (user) {
                    vm.user = user;
                    makeBreadcrumbs(vm.user);
                    setUserDisplayState();
                    formatDatesToLocal(vm.user);
                    vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail && user.email === user.username;
                    //go get the config for the membership provider and add it to the model
                    authResource.getMembershipProviderConfig().then(function (data) {
                        vm.changePasswordModel.config = data;
                        //the user has a password if they are not states: Invited, NoCredentials
                        vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
                        vm.changePasswordModel.config.disableToggle = true;
                        //this is only relavent for membership providers now (it's basically obsolete)
                        vm.changePasswordModel.config.enableReset = false;
                        //in the ASP.NET Identity world, this config option will allow an admin user to change another user's password
                        //if the user has access to the user section. So if this editor is being access, the user of course has access to this section.
                        //the authorization check is also done on the server side when submitted.
                        // only update the setting if not the current logged in user, otherwise leave the value as it is
                        // currently set in the web.config
                        if (!vm.user.isCurrentUser) {
                            vm.changePasswordModel.config.allowManuallyChangingPassword = true;
                        }
                        vm.loading = false;
                    });
                });
            }
            function getLocalDate(date, culture, format) {
                if (date) {
                    var dateVal;
                    var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                    var localOffset = new Date().getTimezoneOffset();
                    var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
                    if (serverTimeNeedsOffsetting) {
                        dateVal = dateHelper.convertToLocalMomentTime(date, serverOffset);
                    } else {
                        dateVal = moment(date, 'YYYY-MM-DD HH:mm:ss');
                    }
                    return dateVal.locale(culture).format(format);
                }
            }
            function toggleChangePassword() {
                vm.changePasswordModel.isChanging = !vm.changePasswordModel.isChanging;
                //reset it
                vm.user.changePassword = null;
            }
            function save() {
                if (formHelper.submitForm({
                        scope: $scope,
                        statusMessage: vm.labels.saving
                    })) {
                    //anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
                    if (vm.user.changePassword) {
                        vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
                    }
                    vm.page.saveButtonState = 'busy';
                    vm.user.resetPasswordValue = null;
                    //save current nav to be restored later so that the tabs dont change
                    var currentNav = vm.user.navigation;
                    usersResource.saveUser(vm.user).then(function (saved) {
                        //if the user saved, then try to execute all extended save options
                        extendedSave(saved).then(function (result) {
                            //if all is good, then reset the form
                            formHelper.resetForm({
                                scope: $scope,
                                notifications: saved.notifications
                            });
                        }, function (err) {
                            //otherwise show the notifications for the user being saved
                            formHelper.showNotifications(saved);
                        });
                        vm.user = _.omit(saved, 'navigation');
                        //restore
                        vm.user.navigation = currentNav;
                        setUserDisplayState();
                        formatDatesToLocal(vm.user);
                        vm.changePasswordModel.isChanging = false;
                        //the user has a password if they are not states: Invited, NoCredentials
                        vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
                        vm.page.saveButtonState = 'success';
                    }, function (err) {
                        contentEditingHelper.handleSaveError({
                            redirectOnFailure: false,
                            err: err
                        });
                        //show any notifications
                        if (err.data) {
                            formHelper.showNotifications(err.data);
                        }
                        vm.page.saveButtonState = 'error';
                    });
                }
            }
            /**
         * Used to emit the save event and await any async operations being performed by editor extensions
         * @param {any} savedUser
         */
            function extendedSave(savedUser) {
                //used to track any promises added by the event handlers to be awaited
                var promises = [];
                var args = {
                    //getPromise: getPromise,
                    user: savedUser,
                    //a promise can be added by the event handler if the handler needs an async operation to be awaited
                    addPromise: function (p) {
                        promises.push(p);
                    }
                };
                //emit the event
                eventsService.emit('editors.user.editController.save', args);
                //await all promises to complete
                var resultPromise = $q.all(promises);
                return resultPromise;
            }
            function goToPage(ancestor) {
                $location.path(ancestor.path).search('subview', ancestor.subView);
            }
            function openUserGroupPicker() {
                vm.userGroupPicker = {
                    view: 'usergrouppicker',
                    selection: vm.user.userGroups,
                    closeButtonLabel: vm.labels.cancel,
                    show: true,
                    submit: function (model) {
                        // apply changes
                        if (model.selection) {
                            vm.user.userGroups = model.selection;
                        }
                        vm.userGroupPicker.show = false;
                        vm.userGroupPicker = null;
                    },
                    close: function (oldModel) {
                        // rollback on close
                        if (oldModel.selection) {
                            vm.user.userGroups = oldModel.selection;
                        }
                        vm.userGroupPicker.show = false;
                        vm.userGroupPicker = null;
                    }
                };
            }
            function openContentPicker() {
                vm.contentPicker = {
                    title: vm.labels.selectContentStartNode,
                    view: 'contentpicker',
                    multiPicker: true,
                    selection: vm.user.startContentIds,
                    hideHeader: false,
                    show: true,
                    submit: function (model) {
                        // select items
                        if (model.selection) {
                            angular.forEach(model.selection, function (item) {
                                if (item.id === '-1') {
                                    item.name = vm.labels.contentRoot;
                                    item.icon = 'icon-folder';
                                }
                                multiSelectItem(item, vm.user.startContentIds);
                            });
                        }
                        // close overlay
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    },
                    close: function (oldModel) {
                        // close overlay
                        vm.contentPicker.show = false;
                        vm.contentPicker = null;
                    }
                };
            }
            function openMediaPicker() {
                vm.mediaPicker = {
                    title: vm.labels.selectMediaStartNode,
                    view: 'treepicker',
                    section: 'media',
                    treeAlias: 'media',
                    entityType: 'media',
                    multiPicker: true,
                    hideHeader: false,
                    show: true,
                    submit: function (model) {
                        // select items
                        if (model.selection) {
                            angular.forEach(model.selection, function (item) {
                                if (item.id === '-1') {
                                    item.name = vm.labels.mediaRoot;
                                    item.icon = 'icon-folder';
                                }
                                multiSelectItem(item, vm.user.startMediaIds);
                            });
                        }
                        // close overlay
                        vm.mediaPicker.show = false;
                        vm.mediaPicker = null;
                    },
                    close: function (oldModel) {
                        // close overlay
                        vm.mediaPicker.show = false;
                        vm.mediaPicker = null;
                    }
                };
            }
            function multiSelectItem(item, selection) {
                var found = false;
                // check if item is already in the selected list
                if (selection.length > 0) {
                    angular.forEach(selection, function (selectedItem) {
                        if (selectedItem.udi === item.udi) {
                            found = true;
                        }
                    });
                }
                // only add the selected item if it is not already selected
                if (!found) {
                    selection.push(item);
                }
            }
            function removeSelectedItem(index, selection) {
                selection.splice(index, 1);
            }
            function disableUser() {
                vm.disableUserButtonState = 'busy';
                usersResource.disableUsers([vm.user.id]).then(function (data) {
                    vm.user.userState = 1;
                    setUserDisplayState();
                    vm.disableUserButtonState = 'success';
                    formHelper.showNotifications(data);
                }, function (error) {
                    vm.disableUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function enableUser() {
                vm.enableUserButtonState = 'busy';
                usersResource.enableUsers([vm.user.id]).then(function (data) {
                    vm.user.userState = 0;
                    setUserDisplayState();
                    vm.enableUserButtonState = 'success';
                    formHelper.showNotifications(data);
                }, function (error) {
                    vm.enableUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function unlockUser() {
                vm.unlockUserButtonState = 'busy';
                usersResource.unlockUsers([vm.user.id]).then(function (data) {
                    vm.user.userState = 0;
                    vm.user.failedPasswordAttempts = 0;
                    setUserDisplayState();
                    vm.unlockUserButtonState = 'success';
                    formHelper.showNotifications(data);
                }, function (error) {
                    vm.unlockUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function resendInvite() {
                vm.resendInviteButtonState = 'busy';
                if (vm.resendInviteMessage) {
                    vm.user.message = vm.resendInviteMessage;
                } else {
                    vm.user.message = vm.labels.defaultInvitationMessage;
                }
                usersResource.inviteUser(vm.user).then(function (data) {
                    vm.resendInviteButtonState = 'success';
                    vm.resendInviteMessage = '';
                    formHelper.showNotifications(data);
                }, function (error) {
                    vm.resendInviteButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function deleteNonLoggedInUser() {
                vm.deleteNotLoggedInUserButtonState = 'busy';
                var confirmationMessage = vm.labels.deleteUserConfirmation;
                if (!confirm(confirmationMessage)) {
                    vm.deleteNotLoggedInUserButtonState = 'danger';
                    return;
                }
                usersResource.deleteNonLoggedInUser(vm.user.id).then(function (data) {
                    formHelper.showNotifications(data);
                    goToPage(vm.breadcrumbs[0]);
                }, function (error) {
                    vm.deleteNotLoggedInUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function clearAvatar() {
                // get user
                usersResource.clearAvatar(vm.user.id).then(function (data) {
                    vm.user.avatars = data;
                });
            }
            function changeAvatar(files, event) {
                if (files && files.length > 0) {
                    upload(files[0]);
                }
            }
            ;
            function upload(file) {
                vm.avatarFile.uploadProgress = 0;
                Upload.upload({
                    url: umbRequestHelper.getApiUrl('userApiBaseUrl', 'PostSetAvatar', { id: vm.user.id }),
                    fields: {},
                    file: file
                }).progress(function (evt) {
                    if (vm.avatarFile.uploadStatus !== 'done' && vm.avatarFile.uploadStatus !== 'error') {
                        // set uploading status on file
                        vm.avatarFile.uploadStatus = 'uploading';
                        // calculate progress in percentage
                        var progressPercentage = parseInt(100 * evt.loaded / evt.total, 10);
                        // set percentage property on file
                        vm.avatarFile.uploadProgress = progressPercentage;
                    }
                }).success(function (data, status, headers, config) {
                    // set done status on file
                    vm.avatarFile.uploadStatus = 'done';
                    vm.avatarFile.uploadProgress = 100;
                    vm.user.avatars = data;
                }).error(function (evt, status, headers, config) {
                    // set status done
                    vm.avatarFile.uploadStatus = 'error';
                    // If file not found, server will return a 404 and display this message
                    if (status === 404) {
                        vm.avatarFile.serverErrorMessage = 'File not found';
                    } else if (status == 400) {
                        //it's a validation error
                        vm.avatarFile.serverErrorMessage = evt.message;
                    } else {
                        //it's an unhandled error
                        //if the service returns a detailed error
                        if (evt.InnerException) {
                            vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage;
                            //Check if its the common "too large file" exception
                            if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf('ValidateRequestEntityLength') > 0) {
                                vm.avatarFile.serverErrorMessage = 'File too large to upload';
                            }
                        } else if (evt.Message) {
                            vm.avatarFile.serverErrorMessage = evt.Message;
                        }
                    }
                });
            }
            function makeBreadcrumbs() {
                vm.breadcrumbs = [
                    {
                        'name': vm.labels.users,
                        'path': '/users/users/overview',
                        'subView': 'users'
                    },
                    { 'name': vm.user.name }
                ];
            }
            function setUserDisplayState() {
                vm.user.userDisplayState = usersHelper.getUserStateFromValue(vm.user.userState);
            }
            function formatDatesToLocal(user) {
                // get current backoffice user and format dates
                userService.getCurrentUser().then(function (currentUser) {
                    user.formattedLastLogin = getLocalDate(user.lastLoginDate, currentUser.locale, 'LLL');
                    user.formattedLastLockoutDate = getLocalDate(user.lastLockoutDate, currentUser.locale, 'LLL');
                    user.formattedCreateDate = getLocalDate(user.createDate, currentUser.locale, 'LLL');
                    user.formattedUpdateDate = getLocalDate(user.updateDate, currentUser.locale, 'LLL');
                    user.formattedLastPasswordChangeDate = getLocalDate(user.lastPasswordChangeDate, currentUser.locale, 'LLL');
                });
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.UserController', UserEditController);
    }());
    (function () {
        'use strict';
        function UserGroupsController($scope, $timeout, $location, userService, userGroupsResource, formHelper, localizationService) {
            var vm = this;
            vm.userGroups = [];
            vm.selection = [];
            vm.createUserGroup = createUserGroup;
            vm.clickUserGroup = clickUserGroup;
            vm.clearSelection = clearSelection;
            vm.selectUserGroup = selectUserGroup;
            vm.deleteUserGroups = deleteUserGroups;
            var currentUser = null;
            function onInit() {
                vm.loading = true;
                userService.getCurrentUser().then(function (user) {
                    currentUser = user;
                    // Get usergroups
                    userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) {
                        // only allow editing and selection if user is member of the group or admin
                        vm.userGroups = _.map(userGroups, function (ug) {
                            return {
                                group: ug,
                                hasAccess: user.userGroups.indexOf(ug.alias) !== -1 || user.userGroups.indexOf('admin') !== -1
                            };
                        });
                        vm.loading = false;
                    });
                });
            }
            function createUserGroup() {
                // clear all query params
                $location.search({});
                // go to create user group
                $location.path('users/users/group/-1').search('create', 'true');
                ;
            }
            function clickUserGroup(userGroup) {
                // only allow editing if user is member of the group or admin
                if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                    return;
                }
                if (vm.selection.length > 0) {
                    selectUserGroup(userGroup, vm.selection);
                } else {
                    goToUserGroup(userGroup.group.id);
                }
            }
            function selectUserGroup(userGroup, selection, event) {
                // Only allow selection if user is member of the group or admin
                if (currentUser.userGroups.indexOf(userGroup.group.alias) === -1 && currentUser.userGroups.indexOf('admin') === -1) {
                    return;
                }
                // Disallow selection of the admin/translators group, the checkbox is not visible in the UI, but clicking(and thus selecting) is still possible.
                // Currently selection can only be used for deleting, and the Controller will also disallow deleting the admin group.
                if (userGroup.group.alias === 'admin' || userGroup.group.alias === 'translator')
                    return;
                if (userGroup.selected) {
                    var index = selection.indexOf(userGroup.group.id);
                    selection.splice(index, 1);
                    userGroup.selected = false;
                } else {
                    userGroup.selected = true;
                    vm.selection.push(userGroup.group.id);
                }
                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                }
            }
            function deleteUserGroups() {
                if (vm.selection.length > 0) {
                    localizationService.localize('defaultdialogs_confirmdelete').then(function (value) {
                        var confirmResponse = confirm(value);
                        if (confirmResponse === true) {
                            userGroupsResource.deleteUserGroups(vm.selection).then(function (data) {
                                clearSelection();
                                onInit();
                                formHelper.showNotifications(data);
                            }, function (error) {
                                formHelper.showNotifications(error.data);
                            });
                        }
                    });
                }
            }
            function clearSelection() {
                angular.forEach(vm.userGroups, function (userGroup) {
                    userGroup.selected = false;
                });
                vm.selection = [];
            }
            function goToUserGroup(userGroupId) {
                $location.path('users/users/group/' + userGroupId).search('create', null);
            }
            onInit();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.GroupsController', UserGroupsController);
    }());
    (function () {
        'use strict';
        function UsersController($scope, $timeout, $location, $routeParams, usersResource, userGroupsResource, userService, localizationService, contentEditingHelper, usersHelper, formHelper, notificationsService, dateHelper) {
            var vm = this;
            var localizeSaving = localizationService.localize('general_saving');
            vm.page = {};
            vm.users = [];
            vm.userGroups = [];
            vm.userStates = [];
            vm.selection = [];
            vm.newUser = {};
            vm.usersOptions = {};
            vm.userSortData = [
                {
                    label: 'Name (A-Z)',
                    key: 'Name',
                    direction: 'Ascending'
                },
                {
                    label: 'Name (Z-A)',
                    key: 'Name',
                    direction: 'Descending'
                },
                {
                    label: 'Newest',
                    key: 'CreateDate',
                    direction: 'Descending'
                },
                {
                    label: 'Oldest',
                    key: 'CreateDate',
                    direction: 'Ascending'
                },
                {
                    label: 'Last login',
                    key: 'LastLoginDate',
                    direction: 'Descending'
                }
            ];
            angular.forEach(vm.userSortData, function (userSortData) {
                var key = 'user_sort' + userSortData.key + userSortData.direction;
                localizationService.localize(key).then(function (value) {
                    var reg = /^\[[\S\s]*]$/g;
                    var result = reg.test(value);
                    if (result === false) {
                        // Only translate if key exists
                        userSortData.label = value;
                    }
                });
            });
            vm.userStatesFilter = [];
            vm.newUser.userGroups = [];
            vm.usersViewState = 'overview';
            vm.selectedBulkUserGroups = [];
            vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
            vm.allowDisableUser = true;
            vm.allowEnableUser = true;
            vm.allowUnlockUser = true;
            vm.allowSetUserGroup = true;
            vm.layouts = [
                {
                    'icon': 'icon-thumbnails-small',
                    'path': '1',
                    'selected': true
                },
                {
                    'icon': 'icon-list',
                    'path': '2',
                    'selected': true
                }
            ];
            vm.activeLayout = {
                'icon': 'icon-thumbnails-small',
                'path': '1',
                'selected': true
            };
            //don't show the invite button if no email is configured
            if (Umbraco.Sys.ServerVariables.umbracoSettings.showUserInvite) {
                vm.defaultButton = {
                    labelKey: 'user_inviteUser',
                    handler: function () {
                        vm.setUsersViewState('inviteUser');
                    }
                };
                vm.subButtons = [{
                        labelKey: 'user_createUser',
                        handler: function () {
                            vm.setUsersViewState('createUser');
                        }
                    }];
            } else {
                vm.defaultButton = {
                    labelKey: 'user_createUser',
                    handler: function () {
                        vm.setUsersViewState('createUser');
                    }
                };
            }
            vm.toggleFilter = toggleFilter;
            vm.setUsersViewState = setUsersViewState;
            vm.selectLayout = selectLayout;
            vm.selectUser = selectUser;
            vm.clearSelection = clearSelection;
            vm.clickUser = clickUser;
            vm.disableUsers = disableUsers;
            vm.enableUsers = enableUsers;
            vm.unlockUsers = unlockUsers;
            vm.openBulkUserGroupPicker = openBulkUserGroupPicker;
            vm.openUserGroupPicker = openUserGroupPicker;
            vm.removeSelectedUserGroup = removeSelectedUserGroup;
            vm.selectAll = selectAll;
            vm.areAllSelected = areAllSelected;
            vm.searchUsers = searchUsers;
            vm.getFilterName = getFilterName;
            vm.setUserStatesFilter = setUserStatesFilter;
            vm.setUserGroupFilter = setUserGroupFilter;
            vm.setOrderByFilter = setOrderByFilter;
            vm.changePageNumber = changePageNumber;
            vm.createUser = createUser;
            vm.inviteUser = inviteUser;
            vm.getSortLabel = getSortLabel;
            vm.toggleNewUserPassword = toggleNewUserPassword;
            vm.copySuccess = copySuccess;
            vm.copyError = copyError;
            vm.goToUser = goToUser;
            function init() {
                vm.usersOptions.orderBy = 'Name';
                vm.usersOptions.orderDirection = 'Ascending';
                if ($routeParams.create) {
                    setUsersViewState('createUser');
                } else if ($routeParams.invite) {
                    setUsersViewState('inviteUser');
                }
                // Get users
                getUsers();
                // Get user groups
                userGroupsResource.getUserGroups({ onlyCurrentUserGroups: false }).then(function (userGroups) {
                    vm.userGroups = userGroups;
                });
            }
            function getSortLabel(sortKey, sortDirection) {
                var found = _.find(vm.userSortData, function (i) {
                    return i.key === sortKey && i.direction === sortDirection;
                });
                return found ? found.label : sortKey;
            }
            function toggleFilter(type) {
                // hack: on-outside-click prevents us from closing the dropdown when clicking on another link
                // so I had to do this manually
                switch (type) {
                case 'state':
                    vm.page.showStatusFilter = !vm.page.showStatusFilter;
                    vm.page.showGroupFilter = false;
                    vm.page.showOrderByFilter = false;
                    break;
                case 'group':
                    vm.page.showGroupFilter = !vm.page.showGroupFilter;
                    vm.page.showStatusFilter = false;
                    vm.page.showOrderByFilter = false;
                    break;
                case 'orderBy':
                    vm.page.showOrderByFilter = !vm.page.showOrderByFilter;
                    vm.page.showStatusFilter = false;
                    vm.page.showGroupFilter = false;
                    break;
                }
            }
            function setUsersViewState(state) {
                if (state === 'createUser') {
                    clearAddUserForm();
                    $location.search('create', 'true');
                    $location.search('invite', null);
                } else if (state === 'inviteUser') {
                    $location.search('create', null);
                    $location.search('invite', 'true');
                } else if (state === 'overview') {
                    $location.search('create', null);
                    $location.search('invite', null);
                }
                vm.usersViewState = state;
            }
            function selectLayout(selectedLayout) {
                angular.forEach(vm.layouts, function (layout) {
                    layout.active = false;
                });
                selectedLayout.active = true;
                vm.activeLayout = selectedLayout;
            }
            function selectUser(user, selection, event) {
                // prevent the current user to be selected
                if (!user.isCurrentUser) {
                    if (user.selected) {
                        var index = selection.indexOf(user.id);
                        selection.splice(index, 1);
                        user.selected = false;
                    } else {
                        user.selected = true;
                        vm.selection.push(user.id);
                    }
                    setBulkActions(vm.users);
                    if (event) {
                        event.preventDefault();
                        event.stopPropagation();
                    }
                }
            }
            function clearSelection() {
                angular.forEach(vm.users, function (user) {
                    user.selected = false;
                });
                vm.selection = [];
            }
            function clickUser(user) {
                if (vm.selection.length > 0) {
                    selectUser(user, vm.selection);
                } else {
                    goToUser(user.id);
                }
            }
            function disableUsers() {
                vm.disableUserButtonState = 'busy';
                usersResource.disableUsers(vm.selection).then(function (data) {
                    // update userState
                    angular.forEach(vm.selection, function (userId) {
                        var user = getUserFromArrayById(userId, vm.users);
                        if (user) {
                            user.userState = 1;
                        }
                    });
                    // show the correct badges
                    setUserDisplayState(vm.users);
                    formHelper.showNotifications(data);
                    vm.disableUserButtonState = 'init';
                    clearSelection();
                }, function (error) {
                    vm.disableUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function enableUsers() {
                vm.enableUserButtonState = 'busy';
                usersResource.enableUsers(vm.selection).then(function (data) {
                    // update userState
                    angular.forEach(vm.selection, function (userId) {
                        var user = getUserFromArrayById(userId, vm.users);
                        if (user) {
                            user.userState = 0;
                        }
                    });
                    // show the correct badges
                    setUserDisplayState(vm.users);
                    // show notification
                    formHelper.showNotifications(data);
                    vm.enableUserButtonState = 'init';
                    clearSelection();
                }, function (error) {
                    vm.enableUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function unlockUsers() {
                vm.unlockUserButtonState = 'busy';
                usersResource.unlockUsers(vm.selection).then(function (data) {
                    // update userState
                    angular.forEach(vm.selection, function (userId) {
                        var user = getUserFromArrayById(userId, vm.users);
                        if (user) {
                            user.userState = 0;
                        }
                    });
                    // show the correct badges
                    setUserDisplayState(vm.users);
                    // show notification
                    formHelper.showNotifications(data);
                    vm.unlockUserButtonState = 'init';
                    clearSelection();
                }, function (error) {
                    vm.unlockUserButtonState = 'error';
                    formHelper.showNotifications(error.data);
                });
            }
            function getUserFromArrayById(userId, users) {
                return _.find(users, function (u) {
                    return u.id === userId;
                });
            }
            function openBulkUserGroupPicker(event) {
                var firstSelectedUser = getUserFromArrayById(vm.selection[0], vm.users);
                vm.selectedBulkUserGroups = _.clone(firstSelectedUser.userGroups);
                vm.userGroupPicker = {
                    title: localizationService.localize('user_selectUserGroups'),
                    view: 'usergrouppicker',
                    selection: vm.selectedBulkUserGroups,
                    closeButtonLabel: localizationService.localize('general_cancel'),
                    show: true,
                    submit: function (model) {
                        usersResource.setUserGroupsOnUsers(model.selection, vm.selection).then(function (data) {
                            // sorting to ensure they show up in right order when updating the UI
                            vm.selectedBulkUserGroups.sort(function (a, b) {
                                return a.alias > b.alias ? 1 : a.alias < b.alias ? -1 : 0;
                            });
                            // apply changes to UI
                            _.each(vm.selection, function (userId) {
                                var user = getUserFromArrayById(userId, vm.users);
                                user.userGroups = vm.selectedBulkUserGroups;
                            });
                            vm.selectedBulkUserGroups = [];
                            vm.userGroupPicker.show = false;
                            vm.userGroupPicker = null;
                            formHelper.showNotifications(data);
                            clearSelection();
                        }, function (error) {
                            formHelper.showNotifications(error.data);
                        });
                    },
                    close: function (oldModel) {
                        vm.selectedBulkUserGroups = [];
                        vm.userGroupPicker.show = false;
                        vm.userGroupPicker = null;
                    }
                };
            }
            function openUserGroupPicker(event) {
                vm.userGroupPicker = {
                    title: localizationService.localize('user_selectUserGroups'),
                    view: 'usergrouppicker',
                    selection: vm.newUser.userGroups,
                    closeButtonLabel: localizationService.localize('general_cancel'),
                    show: true,
                    submit: function (model) {
                        // apply changes
                        if (model.selection) {
                            vm.newUser.userGroups = model.selection;
                        }
                        vm.userGroupPicker.show = false;
                        vm.userGroupPicker = null;
                    },
                    close: function (oldModel) {
                        // rollback on close
                        if (oldModel.selection) {
                            vm.newUser.userGroups = oldModel.selection;
                        }
                        vm.userGroupPicker.show = false;
                        vm.userGroupPicker = null;
                    }
                };
            }
            function removeSelectedUserGroup(index, selection) {
                selection.splice(index, 1);
            }
            function selectAll() {
                if (areAllSelected()) {
                    vm.selection = [];
                    angular.forEach(vm.users, function (user) {
                        user.selected = false;
                    });
                } else {
                    // clear selection so we don't add the same user twice
                    vm.selection = [];
                    // select all users
                    angular.forEach(vm.users, function (user) {
                        // prevent the current user to be selected
                        if (!user.isCurrentUser) {
                            user.selected = true;
                            vm.selection.push(user.id);
                        }
                    });
                }
            }
            function areAllSelected() {
                // we need to check if the current user is part of the selection and 
                // subtract the user from the total selection to find out if all users are selected
                var includesCurrentUser = vm.users.some(function (user) {
                    return user.isCurrentUser === true;
                });
                if (includesCurrentUser) {
                    if (vm.selection.length === vm.users.length - 1) {
                        return true;
                    }
                } else {
                    if (vm.selection.length === vm.users.length) {
                        return true;
                    }
                }
            }
            var search = _.debounce(function () {
                $scope.$apply(function () {
                    getUsers();
                });
            }, 500);
            function searchUsers() {
                search();
            }
            function getFilterName(array) {
                var name = 'All';
                var found = false;
                angular.forEach(array, function (item) {
                    if (item.selected) {
                        if (!found) {
                            name = item.name;
                            found = true;
                        } else {
                            name = name + ', ' + item.name;
                        }
                    }
                });
                return name;
            }
            function setUserStatesFilter(userState) {
                if (!vm.usersOptions.userStates) {
                    vm.usersOptions.userStates = [];
                }
                //If the selection is "ALL" then we need to unselect everything else since this is an 'odd' filter
                if (userState.key === 'All') {
                    angular.forEach(vm.userStatesFilter, function (i) {
                        i.selected = false;
                    });
                    //we can't unselect All
                    userState.selected = true;
                    //reset the selection passed to the server
                    vm.usersOptions.userStates = [];
                } else {
                    angular.forEach(vm.userStatesFilter, function (i) {
                        if (i.key === 'All') {
                            i.selected = false;
                        }
                    });
                    var indexOfAll = vm.usersOptions.userStates.indexOf('All');
                    if (indexOfAll >= 0) {
                        vm.usersOptions.userStates.splice(indexOfAll, 1);
                    }
                }
                if (userState.selected) {
                    vm.usersOptions.userStates.push(userState.key);
                } else {
                    var index = vm.usersOptions.userStates.indexOf(userState.key);
                    vm.usersOptions.userStates.splice(index, 1);
                }
                getUsers();
            }
            function setUserGroupFilter(userGroup) {
                if (!vm.usersOptions.userGroups) {
                    vm.usersOptions.userGroups = [];
                }
                if (userGroup.selected) {
                    vm.usersOptions.userGroups.push(userGroup.alias);
                } else {
                    var index = vm.usersOptions.userGroups.indexOf(userGroup.alias);
                    vm.usersOptions.userGroups.splice(index, 1);
                }
                getUsers();
            }
            function setOrderByFilter(value, direction) {
                vm.usersOptions.orderBy = value;
                vm.usersOptions.orderDirection = direction;
                getUsers();
            }
            function changePageNumber(pageNumber) {
                vm.usersOptions.pageNumber = pageNumber;
                getUsers();
            }
            function createUser(addUserForm) {
                if (formHelper.submitForm({
                        formCtrl: addUserForm,
                        scope: $scope,
                        statusMessage: 'Saving...'
                    })) {
                    vm.newUser.id = -1;
                    vm.newUser.parentId = -1;
                    vm.page.createButtonState = 'busy';
                    usersResource.createUser(vm.newUser).then(function (saved) {
                        vm.page.createButtonState = 'success';
                        vm.newUser = saved;
                        setUsersViewState('createUserSuccess');
                        getUsers();
                    }, function (err) {
                        formHelper.handleError(err);
                        vm.page.createButtonState = 'error';
                    });
                }
            }
            function inviteUser(addUserForm) {
                if (formHelper.submitForm({
                        formCtrl: addUserForm,
                        scope: $scope,
                        statusMessage: 'Saving...'
                    })) {
                    vm.newUser.id = -1;
                    vm.newUser.parentId = -1;
                    vm.page.createButtonState = 'busy';
                    usersResource.inviteUser(vm.newUser).then(function (saved) {
                        //success
                        vm.page.createButtonState = 'success';
                        vm.newUser = saved;
                        setUsersViewState('inviteUserSuccess');
                        getUsers();
                    }, function (err) {
                        //error
                        formHelper.handleError(err);
                        vm.page.createButtonState = 'error';
                    });
                }
            }
            function toggleNewUserPassword() {
                vm.newUser.showPassword = !vm.newUser.showPassword;
            }
            // copy to clip board success
            function copySuccess() {
                vm.page.copyPasswordButtonState = 'success';
            }
            // copy to clip board error
            function copyError() {
                vm.page.copyPasswordButtonState = 'error';
            }
            function goToUser(userId) {
                $location.path('users/users/user/' + userId);
            }
            // helpers
            function getUsers() {
                vm.loading = true;
                // Get users
                usersResource.getPagedResults(vm.usersOptions).then(function (data) {
                    vm.users = data.items;
                    vm.usersOptions.pageNumber = data.pageNumber;
                    vm.usersOptions.pageSize = data.pageSize;
                    vm.usersOptions.totalItems = data.totalItems;
                    vm.usersOptions.totalPages = data.totalPages;
                    formatDates(vm.users);
                    setUserDisplayState(vm.users);
                    vm.userStatesFilter = usersHelper.getUserStatesFilter(data.userStates);
                    vm.loading = false;
                }, function (error) {
                    vm.loading = false;
                });
            }
            function setUserDisplayState(users) {
                angular.forEach(users, function (user) {
                    user.userDisplayState = usersHelper.getUserStateFromValue(user.userState);
                });
            }
            function formatDates(users) {
                angular.forEach(users, function (user) {
                    if (user.lastLoginDate) {
                        var dateVal;
                        var serverOffset = Umbraco.Sys.ServerVariables.application.serverTimeOffset;
                        var localOffset = new Date().getTimezoneOffset();
                        var serverTimeNeedsOffsetting = -serverOffset !== localOffset;
                        if (serverTimeNeedsOffsetting) {
                            dateVal = dateHelper.convertToLocalMomentTime(user.lastLoginDate, serverOffset);
                        } else {
                            dateVal = moment(user.lastLoginDate, 'YYYY-MM-DD HH:mm:ss');
                        }
                        // get current backoffice user and format date
                        userService.getCurrentUser().then(function (currentUser) {
                            user.formattedLastLogin = dateVal.locale(currentUser.locale).format('LLL');
                        });
                    }
                });
            }
            function setBulkActions(users) {
                // reset all states
                vm.allowDisableUser = true;
                vm.allowEnableUser = true;
                vm.allowUnlockUser = true;
                vm.allowSetUserGroup = true;
                var firstSelectedUserGroups;
                angular.forEach(users, function (user) {
                    if (!user.selected) {
                        return;
                    }
                    // if the current user is selected prevent any bulk actions with the user included
                    if (user.isCurrentUser) {
                        vm.allowDisableUser = false;
                        vm.allowEnableUser = false;
                        vm.allowUnlockUser = false;
                        vm.allowSetUserGroup = false;
                        return;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'Disabled') {
                        vm.allowDisableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'Active') {
                        vm.allowEnableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'Invited') {
                        vm.allowEnableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key === 'LockedOut') {
                        vm.allowEnableUser = false;
                    }
                    if (user.userDisplayState && user.userDisplayState.key !== 'LockedOut') {
                        vm.allowUnlockUser = false;
                    }
                    // store the user group aliases of the first selected user
                    if (!firstSelectedUserGroups) {
                        firstSelectedUserGroups = user.userGroups.map(function (ug) {
                            return ug.alias;
                        });
                        vm.allowSetUserGroup = true;
                    } else if (vm.allowSetUserGroup === true) {
                        // for 2nd+ selected user, compare the user group aliases to determine if we should allow bulk editing.
                        // we don't allow bulk editing of users not currently having the same assigned user groups, as we can't
                        // really support that in the user group picker.
                        var userGroups = user.userGroups.map(function (ug) {
                            return ug.alias;
                        });
                        if (_.difference(firstSelectedUserGroups, userGroups).length > 0) {
                            vm.allowSetUserGroup = false;
                        }
                    }
                });
            }
            function clearAddUserForm() {
                // clear form data
                vm.newUser.name = '';
                vm.newUser.email = '';
                vm.newUser.userGroups = [];
                vm.newUser.message = '';
                // clear button state
                vm.page.createButtonState = 'init';
            }
            init();
        }
        angular.module('umbraco').controller('Umbraco.Editors.Users.UsersController', UsersController);
    }());
}());